本文首发于先知社区:从apache-commons-collections中学习java反序列化 - 先知社区 (aliyun.com)
前言
java安全学习的第一篇文章,apache commons collections3.1的反序列化漏洞是java历史上最出名同时也是最具有代表性的反序列化漏洞,废话不多说,我们直接上手分析。希望能帮助到和我一样的初学者。
环境准备
- jdk 1.7版本
- IntelliJ IDEA
- File -> Project Structure ->Modules-> Dependencies ->JARs or directories
- commons-collections-3.1 jar
- 可以直接使用idea自带的maven下载依赖包:
- https://mvnrepository.com/artifact/commons-collections/commons-collections/3.1
基础知识准备
java反射机制
反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。
Java反射在编写漏洞利用代码、代码审计、绕过RASP方法限制等中起到了至关重要的作用。
1 | Java.lang.Class; |
获取反射中的Class对象
1 | #Class.forName 静态方法 |
获取方法
1 | getMethod方法返回一个特定的方法,其中第一个参数为方法名称,后面的参数为方法的参数对应Class的对象 |
反射Runtime执行本地命令
1 | // 获取Runtime类对象 |
Java反序列化
类似php中反序列化使用的魔术方法,比如__destruct函数。在java中,readObject方法在反序列化漏洞时起到了至关重要的作用,利用ObjectInputStream的readObject方法进行对象读取的时候,当readObject()方法被重写的时候,反序列化该类时调用的就是重写的方法。
1 | private void writeObject(ObjectOutputStream oos) //自定义序列化 |
反序列化时会自动调用readObject(ObjectInputStream)方法。我们通过在需要序列化/反序列化的类中定义readObject
和writeObject
方法从而实现自定义的序列化和反序列化操作。
漏洞原理分析
我们在分析cc链反序列化化漏洞的主要思路其实就是两条:
- 利用
InvokerTransformer
、ConstantTransformer
、ChainedTransformer
等类构建反射链,利用java的反射机制,然后通过类中的transformer类来调用。 - 找Common Collections中的类在反序列化时,会触发调用
transform
方法的情况,并以此来构建反序列化漏洞的攻击链。
接下来我们使用IDEA跟进代码进行审计
一、寻找反射链
org/apache/commons/collections/functors/InvokerTransformer
IDEA跟进类中(48~61行):
可以看到此处的transform方法调用了java的反射机制,并且发现this.iMethodName
, this.iParamTypes
, this.iArgs
我们都是可以直接输入的。而input
是在函数调用的时候传入的,我们同样是可控的。
当我们向对应参数传入以下值,即可以调用代码执行:
存在一组可控的反射调用是cc链存在反序列化漏洞的根本原因,但是这里我们只能只能在本地服务器上执行。是无法达成我们想要远程执行命令的效果,这里主要的限制是我们没有没有办法直接传入Runtime类的实例对象。
要想真正的形成调用链,我们仍然需要利用java的反射机制来调用函数,并且至少要调用四个方法:
1 | getMethod(), getRuntime(), exec() ,invoke() |
所以我们之后找到了ChainedTransformer
类。
org/apache/commons/collections/functors/ChainedTransformer
IDEA跟进53~63行
简单的分析代码逻辑,该类的构造函数接受一个数组,我们只需要传入一个数组chainedTransformer
就可以依次去调用每一个类的transform方法。
org/apache/commons/collections/functors/ConstantTransformer
接口函数,在上面的循环中进入了不同的函数。给一个初始的object,然后输出作为下一个输入,从而实现链式调用。
最后的反射poc如下:
1 | Transformer[] transformers = new Transformer[] { |
我们已经构造好了恶意的反射链条,现在我们的目标是触发该类的transform方法。
二、寻找触发链
1 | 某个类的readObject方法 |
- 找到一个
tansform()
方法 , 该方法所属的实例对象是可控的 - 找到一个重写的
readObject()
方法 , 该方法会自动调用transform()
方法.
JDK1.7–TransformedMap利用链
Transmap类在一个元素被添加/删除/或是被修改时,会调用transform方法。我们可以通过TransformedMap.decorate()方法获得一个TransformedMap的实例。
因此,我们可以先构造一个TransformeMap实例,然后修改其中的数据,然后使其自动调用我们之前设定好的transform()方法。
调用链:
1 | ->ObjectInputStream.readObject() |
分析:
首先看/org/apache/commons/collections/map/TransformedMap
1 | protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { |
TransformedMap
中的valueTransformer
在初始化时我们是可控的.
1 | public Object put(Object key, Object value) { |
当执行put方法时会进入transformValue
方法:
1 | protected Object transformValue(Object object) { |
我们可以控制这里的valueTransformer
值为ChianedTransformer即可触发利用链。
但是目前的构造仍然需要Map中的某一项去调用setValue(),我们如果想要在反序列化调用readObject()时直接触发呢?
AbstractInputCheckedMapDecorator类
调用java自带类AnnotationInvocationHandler
中重写的readObject方法,该方法调用时会先将map转为Map.entry,然后执行setvalue操作。
1 | var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6))); |
TransformedMap利用Map.Entry取得第一个值,调用修改值的函数,会触发的setValue()代码
1 | public Object setValue(Object value) { |
接着到了TransoformedMap的checkSetValue()方法。
1 | protected Object checkSetValue(Object value) { |
这里的valueTransformer.transform实际上就是ChianedTransformer类的transform方法。就会触发刚刚我们构造的反射链。
最后的POC:
这里直接上其他大师傅们的poc:
1 | package Serialize2; |
JDK1.8–LazyMap利用链
对于JDK 1.8来说,AnnotationInvocationHandler
类中关键的触发点,setvalue发生了改变。所以我们需要寻找新的类重写readObject来实现调用,
调用链
1 | 反序列化BadAttributeValueExpException |
分析
我们首先看一下LazyMap这个类,这个类也实现了一个map接口:
1 | protected LazyMap(Map map, Transformer factory) |
我们可以看到get方法中如果没有找到key的键值,就会调用factory.transform(key);
,这里的factory变量属于Transformer接口类并且具体使用哪一个类来实例化对象是我们可控的。也就可以形成调用链。
那么如何去自动调用get()方法,跟进TiedMapEntry
类
1 | public TiedMapEntry(Map map, Object key) |
在TiedMapEntry
中,构造时传入使用LazyMap
,调用tostring()
方法,然后紧接着就会调用LazyMap类对象的get方法。
那么到目前为止,我们仍然需要一个类可以在反序列化重写readObject()时可以自动调用toString方法。完整的利用链就可以形成。
BadAttributeValueExpException类
看到BadAttributeValueExpException
的readObject
反序列化方法,调用了toString
方法。
1 | private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { |
其中 valObj
为构造的 TiedMapEntry
类的对象,可以看到其中调用了该类的 toString
函数。
所以,我们只要构造一个BadAttributeValueExpException
对象,并注入我们精心制造的TiedMapEntry
对象。就可在以在反序列时,执行任意命令。
最后的POC
1 | public class Exec { |
参考文章
https://b1ue.cn/archives/166.html
https://www.mi1k7ea.com/2019/02/06/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/
https://www.secpulse.com/archives/137940.html
https://security.tencent.com/index.php/blog/msg/97
https://www.xmanblog.net/java-deserialize-apache-commons-collections/