Commons Collections简介
Commons Collections是Apache软件基金会的一个开源项目,它提供了一组可复用的数据结构和算法的实现,旨在扩展和增强Java集合框架,以便更好地满足不同类型应用的需求。该项目包含了多种不同类型的集合类、迭代器、队列、堆栈、映射、列表、集等数据结构实现,以及许多实用程序类和算法实现。它的代码质量较高,被广泛应用于Java应用程序开发中。本篇文章就是分析Commons Collections3.1版本下的反序列化问题,针对于它的攻击链也被称为cc1链。
准备工作
选择jdk版本为8u65,因为漏洞在8u71的版本就被修复了。下载地址:https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html
CommonsCollections的版本选择3.2.1,不能太高,太高也是没有漏洞的。添加Maven依赖下载:
1 | <dependencies> |
另外,为了方便调试我们还需要java源码,因为源码中大多都是class文件,不方便阅读和查找。所以需要下载openjdk对应的源码,链接:https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4
点击zip下载, 把下载的openjdk里 /share/classes/ 里面的 sun包 复制到 jdk1.8.0_65里
在IDEA中,选择项目结构, 将jdk中的src文件路径添加到源路径中

配置Maven源,要不然没办法下载源码

cc1链分析
利用点
此链的漏洞利用点在Commons Collections库中的Transformer接口

再寻找继承于Transformer接口的类,快捷键alt+crtl+b快速查找。有很多都继承了这个接口,我们定位到InvokerTransformer类中,此类可以被序列化,找到重写的Transform方法

这里通过反射调用任意类的任意方法。其中的参数都是通过该类的构造函数控制,也是我们所能控制的。所以这里应该是可以执行任意方法的。
所以我们可以尝试测试一下能执行恶意类
1 | public class CC1 { |
实例化invokerTransformer类,传入方法名,参数类型,参数值。

运行一下代码,成功弹出计算器。 漏洞利用点有了,接下来就是逆推构造调用链,最终定位到某个类的readObject方法里。
调用链
我们是在transform方法里执行命令的,所以接下来找哪个类调用了transform方法。右键点击查找用法可快速查找什么类调用了transform方法。共有二十四处调用,这些调用的地方我们都可以看看,最后找到TransformedMap类的checkSetValue方法

valueTransformer是通过TransformerMap赋值的, 但是函数类型为protected,只能本类调用。

在该类中还一个decorate方法, 类似于装饰器。它实例化了本类,能够调用TransformedMap构造器并为valueTransformer赋值。

那么回到checkSetValue方法,我们已经可以控制valueTransformer,那么接下来找哪个类的哪个方法调用了该方法。只有一处调用,MapEntry类的setValue方法。

我们知道Entry代表map中的一个键值对,MapEntry类重写了Map的setValue方法
我们先通过对Map的遍历触发setValue方法,主要思路:实例化一个Map,put一个键值对,然后通过TransformedMap的decorate方法进行封装,最后进行遍历
这里的链子就是 Map->setValue->checkSetValue->transformer,通过TransformedMap的decorate方法进行传参。

调试一下
在decorate方法下断点调试一下, 此时,valueTransformer参数赋值为InvokerTransformer对象,步入

此时,valueTransformer参数赋值为InvokerTransformer对象,步入

接着调用构造器,把InvokerTransformer对象赋值给valueTransformer,走到遍历键值对的时候,调用setValue方法

此时就调用了TransformedMap的checkSetValue方法。value的值为Runtime对象。

步入,最后就调用了InvokerTransformer的transform方法。

最终执行命令,弹出计算器。那么就说明此链是走的通的,还是逆向查找,看看哪个类的哪个函数调用了setValue方法,如果是readObject类调用了那就可以进行序列化了。
入口类
查找用法,在AnnotationInvocationHandler类的readObject方法调用了setValue方法。

虽然找到入口点了,但是还要有一些问题需要解决
Runtime类不能被反序列化
可以通过反射获取到它的class对象,class对象是可以被反序列化的,跟进看一下:

一般的反射执行命令的写法为:
1 | public class CC1test1 { |
我们要把这个写法嵌套在 InvokerTransformer类里面
首先获得runtime方法
1 | Method getMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class); |
为什么要这样写我们可以跟进一下InvokerTransformer方法和getMethod方法
InvokerTransformer需要传递三个参数,一个是参数名称,一个是Class数组,一个是Object数组。

getMethod方法需要传递两个参数,一个是参数明,还有一个是Class数组

获取Runtime实例
1 | Runtime runtime = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getMethod); |
获取exec函数调用runtime示例执行calc。
1 | new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(runtime); |
这样就把命令执行嵌入 InvokerTransformer方法里了。成功执行

但是这样每次就需要调用一次transform方法,传入上一步的输出。很麻烦
在Commons Collections库中存在ChainedTransformer这么一个类,可以将多个Transformer串联在一起形成一个链,递归调用。跟进这个类看一下

它的构造方法传递一个数组,它的transform方法将iTransformers进行递归调用。代码如下

完美解决不能反序列化的问题。
AnnotationInvocationHandler不能在包外实例化
看代码

构造器没写类型,那就是默认访问修饰符,只能在包内实例化。所以还是需要反射来获得实例对象。
该构造器接收两个参数,分别是注解类型的Class对象和Map对象。

不能直接实例化,我们就需要用反射去调用他

1 | Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");//获取类名 |
满足readObject的两个if判断
实际上这条链根本就没有调用setValue方法,打断点调试看看。断点直接打在readObject方法


第一个if就进不去,这个memberType实际上就是获取注解对象名为name的值,这个name,就是memberValues的键名。而这个memberValues是什么呢?我们创建的Map对象的键值,我们设置的Override,跟进看一下

是没有值的,所以需要换一个注解,跟进Target看一下

是有value值。同时对于Map对象,需要put键值对,键名必须为value,键值随意。更改完成后,再跟进看一下


这是var7有值的。
继续跟进
setValue方法的参数不可控
实际上执行了setValue方法后会跟进到checkSetValue。

而这里的value不是我们想要的Runtime.class,也就是不可控。可以利用一个类ConstantTransformer,跟进看一下它的transform方法

无论接收什么参数,返回一个固定值,而这个固定值可以通过构造器可控。也就是说,无论value被赋上什么值,只要它调用了ConstantTransformer的transform方法,结果我们都可控。在Transformer数组里实例化ConstantTransformer

然后调试看一下

在数组遍历时会调用transform将输入改变为Runtime对象。

完整代码
1 | import org.apache.commons.collections.functors.ChainedTransformer; |