Commons-Collections2原理分析
在2015年底commons-collections反序列化利⽤链被提出时,Apache Commons Collections有以下两个分⽀版本:
- commons-collections:commons-collections
- org.apache.commons:commons-collections4
可⻅,groupId和artifactId都变了。官⽅认为旧的commons-collections有⼀些架构和API设计上的问题,但修复这些问题,会产⽣⼤量不能向前兼容的改动。所以,commons-collections4不再认为是⼀个⽤来替换commons-collections的新版本,⽽是⼀个新的包,两者的命名空间不冲突,因此可以共存在同⼀个项⽬中。
其中LazyMap被改名为lazyMap,所以3.2.1中存在反序列化利⽤链修改一下还是可以用在4.0版本中的。
前置知识
PriorityQueue
PriorityQueue 优先级队列是基于优先级堆(a priority heap)的一种特殊队列,他给每个元素定义“优先级”,这样取出数据的时候会按照优先级来取。默认情况下,优先级队列会根据自然顺序对元素进行排序。
因此,放入PriorityQueue的元素,必须实现 Comparable 接口,PriorityQueue 会根据元素的排序顺序决定出队的优先级。如果没有实现 Comparable 接口,PriorityQueue 还允许我们提供一个 Comparator 对象来判断两个元素的顺序。PriorityQueue 支持反序列化,在重写的 readObject 方法中,将数据反序列化到 queue 中之后,会调用 heapify() 方法来对数据进行排序。
排序是靠将⼤的元素下移实现的。siftDown()是将节点下移的函数,⽽comparator.compare()⽤来⽐较两个元素⼤⼩heapify() 方法调用 siftDown() 方法,在 comparator 属性不为空的情况下,调用 siftDownUsingComparator() 方法

在 siftDownUsingComparator() 方法中,会调用 comparator 的 compare() 方法来进行优先级的比较和排序。
这样,反序列化之后的优先级队列,也拥有了顺序。
TransformingComparator
TransformingComparator实现了java.util.Comparator接⼝,这个接⼝⽤于定义两个对象如何进⾏⽐较。siftDownUsingComparator()中就使⽤这个接⼝的 compare()⽅法⽐较树的节点。
TransformingComparator 初始化时配置 Transformer 和 Comparator,如果不指定 Comparator,则使用 ComparableComparator.<Comparable>comparableComparator()。
在调用 TransformingComparator 的 compare 方法时,可以看到调用了 this.transformer.transform() 方法对要比较的两个值进行转换,然后再调用 compare 方法比较。

TemplatesImpl
其中, setFieldValue 方法用来设置私有属性。这里设置了三个属性: _bytecodes 、_name 和 _tfactory。_bytecodes 是由字节码组成的数组;_name 可以是任意字符串,只要不为null即可;_tfactory 需要是一个 TransformerFactoryImpl 对象,因为TemplatesImpl#defineTransletClasses()方法里有调用到 _tfactory.getExternalExtensionsMap(),如果是null会出错。但那jdk1.8.312/jdk1.8.05举例,在TemplatesImpl#readObject()中,会对_tfactory赋初始值,即_tfactory = new TransformerFactoryImpl();,所以_tfactory为空也可以
另外,值得注意的是, TemplatesImpl 中对加载的字节码是有一定要求的:这个字节码对应的类必须是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类。

建议读一下《java漫谈13》,里面详细介绍了TemplatesImpl类以及执行TransletClassLoader#defineClass()的调用链
构造带有数组的POC

- 先是和之前一样,构造命令链,然后放到
ChainedTransformer中,但此时应该构造一个fakeTransformers放到ChainedTransformer中,以免在反序列化之前(本地调试的时候)就将代码执行。 - 然后构造
TransformingComparator对象,将ChainedTransformer对象放进去,这样在调用TransformingComparator#compare方法时,就能调用到ChainedTransformer.transform() - 接着构造
PriorityQueue对象,将TransformingComparator对象放进去,这样就指定了Comparator。然后随便放入两个元素。
将真正的恶意Transformer设置上 - 进行序列化。
这样,在反序列化时,会调用PriorityQueue的readObject,然后调用heapify();、siftDown()、siftDownUsingComparator(),然后就会调用comparator.compare(),这里的comparator其实就是上面传入的TransformingComparator。
此时进入到TransformingComparator#compare执行this.transformer.transform() ,也就回到CC1中的ChainedTransformer#transform,继而调用ConstantTransformer.transform()和InvokerTransformer.transform()。
这里会触发两个计算器,因为在进入TransformingComparator#compare后,执行了两次this.transformer.transform()。
构造没有数组的POC
ysoserial 的 CC2 没有使用 ChainedTransformer,而直接使用了 InvokerTransformer 配合 TemplatesImpl 直接加载恶意类的 bytecode。
触发逻辑为:
- 创建恶意的
TemplatesImpl对象,写入_bytecodes、_name、_tfactory(这个属性没写也测试成功了)属性 - 构建一个无害的
InvokerTransformer正常第一个参数是1
Transformer transformer = new InvokerTransformer("toString", null, null);
newTransformer,但为了避免调试过程中触发恶意类,就先传一个普通类。 - 然后构造
TransformingComparator对象,将InvokerTransformer对象放进去,这样在调用TransformingComparator#compare方法时,就能调用this.transformer.transform() - 接着构造
PriorityQueue对象,将TransformingComparator对象放进去,这样就指定了Comparator。 - 因为我们这⾥⽆法再使⽤
Transformer数组,所以也就不能⽤ConstantTransformer来初始化变量,需要接受外部传⼊的变量。⽽在Comparator#compare()时,队列⾥的元素将作为参数传⼊transform()⽅法,这就是传给TemplatesImpl#newTransformer的参数。 - 所以,将
TemplatesImpl对象传入queue中,并将toString⽅法改成恶意⽅法newTransformer
为什么newTransformer是恶意⽅法?因为templatesImpl.newTransformer();这个代码就是执行TemplatesImpl中的字节码的。
(su18使用反射将恶意的TemplatesImpl对象写入到PriorityQueue的queue中。)
这里指弹出一个计算器,应该是执行到第一个this.transformer.transform()后,报错导致程序提前结束
总结
利用说明:
利用 PriorityQueue 在反序列化后会对队列进行优先级排序的特点,为其指定 TransformingComparator 排序方法,并在其中为其添加 Transforer,与 CC1 类似,主要的触发位置还是 InvokerTransformer。
1 | Gadget 总结: |
1 | 调用链展示: |
依赖版本
commons-collections4 : 4.0
注意
PriorityQueue的利⽤链不⽀持在commons-collections 3中使⽤。
因为类org.apache.commons.collections4.comparators.TransformingComparator在commonscollections4.0以前是版本中是没有实现Serializable接⼝的,⽆法在序列化中使⽤。修复
3.2.2
新版代码中增加了⼀个⽅法FunctorUtils#checkUnsafeSerialization,⽤于检测反序列化是否安全。如果开发者没有设置全局配置org.apache.commons.collections.enableUnsafeSerialization=true,即默认情况下会抛出异常。
这个检查在常⻅的危险Transformer类(InstantiateTransformer、InvokerTransformer、PrototypeFactory、CloneTransformer等)的readObject⾥进⾏调⽤,所以,当我们反序列化包含这些对象时就会抛出⼀个异常:
1 | Serialization support for org.apache.commons.collections.functors.InvokerTransformer is |
4.1
4.1⾥,这⼏个危险Transformer类不再实现 Serializable 接⼝,也就是说,他们⼏个彻底⽆法序列化和反序列化了。
