CommonsCollections2


触发POC

利用IDEA创建一个maven项目,导入依赖

 

利用ysoserial生成payload:

得到

这里用的jdk1.8.0_121

触发Poc

当然也可以是

 

执行后发现能成功弹出计算器

 

分析利用链

类似地,这个漏洞最终是利用的是org.apache.commons.collections4.functors.InvokerTransformertransform()

直接下个断点查看调用栈

CC1我们是从最后往前分析,这里我们反过来分析

 

Step 0

入口点是java.util.PriorityQueuereadObject()方法

看起来并没有什么东西

 

Step 1

于是跟入调用的heapify()

这里的queue定义为transient Object[] queue;

queue参数是根据PriorityQueuee二参构造方法的第一个参数传入的int类型

并根据传入的int值大小new一个相应大小的一维对象数组

如果这里size的值大于等于2则能进入siftDown()

 

Step 2

siftDown():

判断了下comparator是否为null,然后继续跟入第三行的siftDownUsingComparator()

PS.

方法参数中的E xE在是泛型通配符

通常:

?: 不确定的类型

T (Type) : 一个类型

K V (Key Value) : 键 值

E (Element) : 程序的一个元素

......

 

Step 3

siftDownUsingComparator():

这里有2处调用compare()方法,二者都是根据comparator调用的

根据上面提到的PriorityQueue的构造函数,得知comparator来自于的其二参构造方法的第二个参数Comparator<? super E> comparator

那有啥compare()可以利用呢

PS.

这个利用链是触发的comparator.compare(x, (E) c)(第二个, 第10行)

 

Step 4

恰好org.apache.commons.collections4.comparators.TransformingComparatorcompare()方法为:

如果我们控制PriorityQueue里的comparatororg.apache.commons.collections4.comparators.TransformingComparator即可调用到这里

然后TransformingComparatorthis.transformer又是可控的

 

Step 5

最后利用org.apache.commons.collections4.functors.InvokerTransformertransform()方法可以来执行任意可反序列化对象的任意public方法

这里就会有疑问了,那怎么RCE呢,CC1不是分析过,要调用多次transform串起来才行吗?

 

Step 6

这里需要用到com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl内置类

这个类可以反序列化,并且其类中有可以直接执行字节码的方法,且其构造方法能够直接赋值_bytecodes ,我们可以利用这个类来想办法来任意代码执行

而其类中的defineTransletClasses()方法可以_bytecodes作为class的字节码并加载,把新的类存入_class属性 (第25行defineClass()作用是把字节码转化为class)

这里新加载类的父类必须是ABSTRACT_TRANSLETcom.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet),否则会走到else分支报错

这样我们得到的_class[i]中都是直接的类了

接着在getTransletInstance()中会调用新加载类的newInstance()对其进行实例化,并返回其结果,也就是会执行我们传入_bytecodes中的静态代码(具体原理见下面的利用Java字节码执行恶意代码部分)

虽然说这是一个private方法

但是TemplatesImpl中的newTransformer()方法是public的,这样就可以直接调用了

到此,攻击链分析完毕

 

利用Java字节码执行恶意代码

在程序运行时,操作字节码可以实现动态生成新的类动态改变某个类的结构(添加/删除/修改 新的属性/方法)

主要用到的是javassist,提供了在运行时操作Java字节码的方法,如在已有Class中动态修改和插入Java代码

先本地构造一个简单的例子:

需要先导入javassistrhino

Test.java:

(其实不设置clazz.setName("ccc");也是可以的,这样在defineClass时只需要从Test重新取就好了)

我们稍微修改一下代码(在Test中添加了静态代码段,修改了code内容),如下

Thread.currentThread().getStackTrace()[1].getClassName() 是获取当前类名

可以很清晰地看明白其过程

 

构造EXP

Exp 只能从后往前一步步构造了

Step 6

由上面的Step 5我们已经可以来执行任意可反序列化对象的任意public方法,现在就是要通过

来构造静态代码块执行

所以这里input我们要传入com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

this.iMethodName需要为那个public方法,也就是newTransformer,这样就能执行到getTransletInstance()了,若_name赋值且_class为空则进入defineTransletClasses()

但此时_name_class等参数并没有进行赋值

还需要利用TemplatesImpl的构造方法来对其赋值,且其构造方法是protected,需要通过反射的方式来进行实例

TemplatesImpl有个public的无参构造方法,用这个直接new好后再利用set赋值

这样进入defineTransletClasses

这里如果_tfactorynull会报错,于是要

新加载类的父类必须是ABSTRACT_TRANSLETcom.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet

exp part 1:

下面再将手动构造的transform改成从代码触发

 

Step 5

直接先引入org.apache.commons.collections4.functors.InvokerTransformer

于是exp part 2

 

Step 4

然后看到org.apache.commons.collections4.comparators.TransformingComparatorcompare,这里触发上面的transform

this.transformer需要传入为InvokerTransformer类,且obj1需要为templatesimpl

上面分析知compare()方法的执行也是其他类而来的

因为Step4一直到Step1其实都是在一个类下,这里先不再分开写exp,待会再针对这个类一起写

 

Step 3

java.util.PriorityQueuesiftDownUsingComparator()

传入了xc

x来自于siftDownUsingComparator()的第二个参数

c = queue[child] ,而child是将传入的第一个参数乘上2再加1

关注第一个x参数,也别忘了这个comparator,这个comparator需要设置为org.apache.commons.collections4.comparators.TransformingComparator

 

Step 2

siftDown()

传入siftDownUsingComparator()的两个参数也是传入siftDown()的参数

上面的x参数正是siftDown()这里传入的第二个参数x

 

Step 1

追溯到heapify()

两个参数由sizequeue决定,这里size需要大于等于2才能执行siftDown()且为2时也满足siftDownUsingComparator()前面的一些判断

如果为size为2时,上面x参数来源则是queue[0],所以queue[0]需要为templatesimpl

那么继续构造

exp part 3:

然后输出反序列化数据到文件即可

 

整合Exp

_bytecodes 是由字节码组成的数组

_name 可以是任意字符串,只要不为null即可

_tfactory 需要是一个TransformerFactoryImpl对象

因为TemplatesImpl#defineTransletClasses()方法里有调用到_tfactory.getExternalExtensionsMap(),如果是null会出错