Commons-Collections6原理分析
在 CC5 中我们使用了 TiedMapEntry#toString 来触发 LazyMap#get,在 CC6 中是通过 TiedMapEntry#hashCode 来触发。之前看到了 hashcode 方法也会调用 getValue() 方法然后调用到其中 map 的 get 方法触发 LazyMap,那我们如何在反序列化时触发 TiedMapEntry 的 hashcode 方法呢?
在 URLDNS 中,在反序列化一个 HashMap 对象时,会调用 key 对象的 hashCode 方法计算 hash 值。那在此处当然也可以用来触发 TiedMapEntry 的 hashCode 方法。

那就要面临在 URLDNS 中同样面临的问题:调用链会在 HashMap 的 put 方法调用时提前触发,需要想办法绕过触发,可以采用以下几种方式:
- 类似
URLDNS2的利用反射调用putVal方法写入key避免触发。 - 在向
HashMap push LazyMap时先给个空的ChainedTransformer,这样添加的时候不会执行任何恶意动作,put之后再反射将有恶意链的Transformer数组写到ChainedTransformer中。但需要LazyMap的key进行remove。
这样就完成了一个 HashMap 的触发方式。
但 CC6 使用的是 HashSet 触发方式。
前置知识
HashSet
HashSet 是一个无序的,不允许有重复元素的集合。HashSet 本质上就是由 HashMap 实现的。HashSet 中的元素都存放在 HashMap 的 key 上面,而 value 中的值都是统一的一个private static final Object PRESENT = new Object();。HashSet 跟 HashMap 一样,都是一个存放链表的数组。
在 HashSet 的 readObject 方法中,会调用其内部 HashMap 的 put 方法,将值放在 key 上。
POC
CC6WithHashMap
1 | Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)}; |
为什么需要map.remove("key1");
关键点在LazyMap的get⽅法,就是最后触发命令执⾏的transform(),但是这个if语句并没有进⼊,因为map.containsKey(key)的结果是true,理想结果是false。

原因是HashMap的put⽅法中,也有调⽤到 hash(key),进而执行了TiedMapEntry#hashCode(),触发了TiedMapEntry#hashCode(),最后执行LazyMap#get,这是在构造payload时执行的LazyMap#get,触发了fakeTransformers,导致提前执行了一遍虚假链,设了key为key1,这样在反序列化再次执行LazyMap#get时,由于设了key为key1,这个if语句并没有进⼊,导致执行失败。
(new TiedMapEntry(outerMap, "key1");里key1是形参key的实参,这里的key指的是这个意思。)
我们的解决⽅法也很简单,只需要将key1这个Key,再从map中移除即可:map.remove("key1") 。
CC6withHashSet
1 | Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)}; |
这里我只是简单的在 HashMap 之外嵌套了一层 HashSet。在 ysoserial 中的 CC6 payload 中,作者 matthias_kaiser 多次使用反射向 HashMap 及 HashSet 中写入值,并兼容了 JDK 7 和 8 中成员变量名发生变化的情况。并且是通过向底层 map 中的节点添加的方式。
总结
利用说明:HashMap#readObject 调用 HashMap#hash 继而调用 key.hashCode ,也就是 TiedMapEntry 的 hashCode ,继而调用 TiedMapEntry 的 getValue ,最后调用 LazyMap 的 get 方法。HashSet#readObject 调用 map.put() ,这个 map 就是 HashMap ,然后 HashMap#put 调用 hash(key) ,后面与 HashMap 利用链一样。
Gadget 总结:
1 | kick-off gadget:java.util.HashSet#readObject()/java.util.HashMap#readObject() |
su18写的调用链:
1 | HashSet.readObject()/HashMap.readObject() |
依赖版本
commons-collections : 3.1~3.2.1
