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
接⼝,也就是说,他们⼏个彻底⽆法序列化和反序列化了。