Java反序列化CommonsCollections——CC1(TransformMap)
Java反序列化CommonsCollections——CC1
前言
今天起开始讲CommonsCollections反序列化漏洞,相当经典,丰富一下知识区。
背景介绍
Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper(是一些已发布的项目)、Sandbox(是一些正在开发的项目)和Dormant(是一些刚启动或者已经停止维护的项目)。
Commons Collections包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。
包结构:
以下是Collections的包结构和简单介绍,如果你想了解更多的各个包下的接口和实现,请参考Apache Commons Collections 3.2.2 API文档。
org.apache.commons.collections– CommonsCollections自定义的一组公用的接口和工具类org.apache.commons.collections.bag– 实现Bag接口的一组类org.apache.commons.collections.bidimap– 实现BidiMap系列接口的一组类org.apache.commons.collections.buffer– 实现Buffer接口的一组类org.apache.commons.collections.collection–实现java.util.Collection接口的一组类org.apache.commons.collections.comparators– 实现java.util.Comparator接口的一组类org.apache.commons.collections.functors–Commons Collections自定义的一组功能类org.apache.commons.collections.iterators– 实现java.util.Iterator接口的一组类org.apache.commons.collections.keyvalue– 实现集合和键/值映射相关的一组类org.apache.commons.collections.list– 实现java.util.List接口的一组类org.apache.commons.collections.map– 实现Map系列接口的一组类org.apache.commons.collections.set– 实现Set系列接口的一组类
准备工作
- JDK8u65(CC 链的漏洞还未被修掉)
- openJDK 8u65
先去官网下载8u65版本的jdk
这里我遇到了一个问题,它一直给我下8u111,无奈只能去其他网站上找8u65版本的jdk。我是下载后在虚拟机里安装,再将对应的jdk文件复制到本地主机上调试。
因为我们打开源码,很多地方的文件是 .class 文件,是已经编译完了的文件,都是反编译代码,我们很难读懂,所以需要把它转换为 .java 文件。
来去下载openJDK,将\src\share\classes目录下的sun文件复制到jdk1.8.0_65\src路径下
在SDKs源路径添加我们新解压的文件

这样再去看源码就一目了然了。
CC1攻击链分析
Transformer接口的作用就是它接受一个对象,调用transform方法对这个对象进行一系列操作,我们暂且可以将它理解为装饰器、代理这个功能。
ctrl + alt + B,查看实现接口的类
在InvokerTransformer类中存在一个反射调用任意类,可以作为链子的终点去利用。
看到这里有漏洞,我们先尝试构造一下,调用这个类的弹计算器。
在调用这个类之前,我们先回顾一下反射的命令执行的代码。
1 | |
接下来我们构造一个利用 InvokerTransformer 类弹计算器的程序。
根据构造方法构造 EXP,因为是 public 的方法,这里无需反射。
需要三个参数:参数名、参数类型、参数值
1 | |
成功的弹出计算器,事实上就是通过InvokerTransformer重新实现了一个反射
我们在整个链子中是走到了最后一步,即调用了InvokerTransformer.transform(),那么接下来要做的就是往回倒退,谁调用了transform,谁又调用了调用了transform的方法,以此类推。
点进transform方法,右键find usages查询调用。
我们可以一步步看有什么比较常用的,这里为节省时间,直接来看TransformedMap类中的checkSetValue()方法。
那这个valueTransformer是什么呢?我们接着去看看它的构造函数。它的构造函数是一个protected,意思说它肯定是要被自己调用的,这个TransformedMap可以理解为接受一个Map进来,对这个Map的key和value进行一系列操作,这些操作都写在这些xxxTransformer里面了。
在 decorate() 静态方法中创建了 TransformedMap 对象,完成了装饰的操作
我们可以从这里开始试一下编写poc,这里我们从后往前推,如果有一个遍历数组的地方调用了setValue,就能接上这后半条链。
1 | |
总结一下上面的链子 1、执行decorate方法的时候,会新建 TransformedMap 对象。2、调用对象的 checkSetValue 方法(因为我们无法直接获取 TransformedMap 对象,它的作用域是 protected
3、checkSetValue最终会走到transform方法,即重点的危险方法。
目前找到的链子位于 checkSetValue 当中,去找 .decorate 的链子,发现无法进一步前进了,所以我们回到 checkSetValue 重新找链子。
继续 find usages,找到了 parent.checkSetValue(value); 调用了 checkSetValue
发现这是一个抽象类,是 TransformedMap 的父类。调用 checkSetValue 方法的类是 AbstractInputCheckedMapDecorator 类中的一个内部类 MapEntry
setValue() 实际上就是在 Map 中对一组 entry(键值对)进行 setValue() 操作。所以,我们在进行 .decorate 方法调用,进行 Map 遍历的时候,就会走到 setValue() 当中,而 setValue() 就会调用 checkSetValue
到此处,我们的攻击思路出来了,找到一个是数组的入口类,遍历这个数组,并执行 setValue 方法,我们就可以把我们这个TransformedMap传进去。所以接下来我们要去找谁执行了setValue()方法,最理想的情况下,如果有一个对象的readObject()里面调用了setValue()方法就最好不过了!
于是我们接着对setValue()进行find usages,发现真的有一个readObject类里面调用了setValue方法。
根据这个类的名字可以得知它是动态代理过程中的调用处理器类。
看一下构造函数,接收两个参数,第一个是个Class对象,第二个是个Map,这个Map是我们可控的,因为在构造函数里,我们可以将我们构造好的TransformedMap传进去,传进去后实例化这个类并尝试调用它。
因为AnnotationInvocationHandler 的作用域为 default,我们需要通过反射的方式来获取这个类及其构造函数,再实例化它。
我们很自然的就写了这个poc:
1 | |
然而理想很丰满,现实很骨感,这个poc还是存在几个问题:1、runtime对象不能被序列化 2、实际传参不是runtime对象 3、需要满足这两个if
先来解决第一点,Runtime不能被序列化,但是Runtime.class是能被序列化的,我们写一遍普通反射
1 | |
接着,我们将这个反射的 Runtime 改造为使用 InvokerTransformer 调用的方式。
1 | |
这里Transformer不断调用前者,是一个循环调用。
这里我们用ChainTransformer类,这个类下的transform 方法递归调用了前一个方法的结果,作为后一个方法的参数。修改后的poc如下
1 | |
接下来我们要着手解决这两个if条件的判断了
查看代码,第一个if跳出去是因为memberType为null,memberType是先获取memberValue,然后对其key方法,再去获取memberType,我们只需要针对性的修改,传入的注解参数,是有成员变量即可。

这一次的运行我们成功进入到了 setValue 方法当中,但还是不能够进行弹计算器,这是因为 setValue() 处中的参数并不可控,而是指定了 AnnotationTypeMismatchExceptionProxy 类,是无法进行命令执行的。
在一开始曾介绍过一个类ConstantTransformer,他有两个特点:1、构造方法:传入的任何对象都放在 iConstant 中。 2、transform方法:无论传入什么,都返回iConstant
那么我们可以利用这一点,将 AnnotationTypeMismatchExceptionProxy 类作为 transform() 方法的参数,也就是这个无关的类,作为参数,我们先传入一个 Runtime.class,然后无论 transform() 方法会调用什么对象,都会返回 Runtime.class
最终EXP:
1 | |
执行后,成功弹出计算器!!大功告成
总结:这条链子的顺序为
AnnotationInvocationHandler.readObject() –> AbstractInputCheckedMapDecorator.setValue() –> TransformedMap.checkSetValue() –>InvokerTransformer.transform()
并且用了ChainedTransformer类实现递归调用、ConstantTransformer类实现控制初始setValue()的值加以辅助。

后记
CC1可以说是最重要的一条链子了,反序列化链子虽说很多,但八九不离十,理解了这一条就理解的七七八八了。过程中还是有相当多坑的地方的,有点绕。主要还是根据它写的方法修修改改,最核心还是InvokerTransformer的任意方法执行。