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() 方法来对数据进行排序。
1
排序是靠将⼤的元素下移实现的。siftDown()是将节点下移的函数,⽽comparator.compare()⽤来⽐较两个元素⼤⼩
heapify() 方法调用 siftDown() 方法,在 comparator 属性不为空的情况下,调用 siftDownUsingComparator() 方法
2
3
siftDownUsingComparator() 方法中,会调用 comparatorcompare() 方法来进行优先级的比较和排序。
4
这样,反序列化之后的优先级队列,也拥有了顺序。

TransformingComparator

TransformingComparator实现了java.util.Comparator接⼝,这个接⼝⽤于定义两个对象如何进⾏⽐较。siftDownUsingComparator()中就使⽤这个接⼝的 compare()⽅法⽐较树的节点。

TransformingComparator 初始化时配置 TransformerComparator,如果不指定 Comparator,则使用 ComparableComparator.<Comparable>comparableComparator()
5
在调用 TransformingComparatorcompare 方法时,可以看到调用了 this.transformer.transform() 方法对要比较的两个值进行转换,然后再调用 compare 方法比较。

6

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的子类。

CC2补充1

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

构造带有数组的POC

7

  • 先是和之前一样,构造命令链,然后放到ChainedTransformer中,但此时应该构造一个fakeTransformers放到ChainedTransformer中,以免在反序列化之前(本地调试的时候)就将代码执行。
  • 然后构造TransformingComparator对象,将ChainedTransformer对象放进去,这样在调用 TransformingComparator#compare 方法时,就能调用到 ChainedTransformer.transform()
  • 接着构造PriorityQueue对象,将TransformingComparator对象放进去,这样就指定了Comparator。然后随便放入两个元素。
    将真正的恶意Transformer设置上
  • 进行序列化。

这样,在反序列化时,会调用PriorityQueuereadObject,然后调用heapify();siftDown()siftDownUsingComparator(),然后就会调用comparator.compare(),这里的comparator其实就是上面传入的TransformingComparator
此时进入到TransformingComparator#compare执行this.transformer.transform() ,也就回到CC1中的ChainedTransformer#transform,继而调用ConstantTransformer.transform()InvokerTransformer.transform()
这里会触发两个计算器,因为在进入TransformingComparator#compare后,执行了两次this.transformer.transform()
8

构造没有数组的POC

ysoserialCC2 没有使用 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 对象写入到 PriorityQueuequeue 中。)
    这里指弹出一个计算器,应该是执行到第一个this.transformer.transform()后,报错导致程序提前结束

总结

利用说明:
利用 PriorityQueue 在反序列化后会对队列进行优先级排序的特点,为其指定 TransformingComparator 排序方法,并在其中为其添加 Transforer,与 CC1 类似,主要的触发位置还是 InvokerTransformer

1
2
3
4
Gadget 总结:
kick-off gadget:java.util.PriorityQueue#readObject()
chain gadget:org.apache.commons.collections4.comparators.TransformingComparator#compare()
sink gadget:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer()
1
2
3
4
5
6
调用链展示:
PriorityQueue.readObject()
TransformingComparator.compare()
*ChainedTransformer.transform()
InvokerTransformer.transform()
TemplatesImpl.newTransformer()

依赖版本

commons-collections4 : 4.0

注意

  1. PriorityQueue的利⽤链不⽀持在commons-collections 3中使⽤。
    因为类 org.apache.commons.collections4.comparators.TransformingComparatorcommonscollections4.0以前是版本中是没有实现 Serializable 接⼝的,⽆法在序列化中使⽤。

  2. 修复3.2.2
    新版代码中增加了⼀个⽅法FunctorUtils#checkUnsafeSerialization ,⽤于检测反序列化是否安全。如果开发者没有设置全局配置 org.apache.commons.collections.enableUnsafeSerialization=true ,即默认情况下会抛出异常。
    这个检查在常⻅的危险Transformer类( InstantiateTransformerInvokerTransformerPrototypeFactoryCloneTransformer 等)的 readObject ⾥进⾏调⽤,所以,当我们反序列化包含这些对象时就会抛出⼀个异常:

1
2
3
4
Serialization support for org.apache.commons.collections.functors.InvokerTransformer is
disabled for security reasons. To enable it set system property
'org.apache.commons.collections.enableUnsafeSerialization' to 'true', but you must ensure
that your application does not de-serialize objects from untrusted sources.

4.1
4.1⾥,这⼏个危险Transformer类不再实现 Serializable 接⼝,也就是说,他们⼏个彻底⽆法序列化和反序列化了。