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