簡介
URLDNS 這個利用鏈主要用來檢測是否存在反序列化漏洞,有如下兩個優點:
- 使用java 內部的類進行構造,不依賴第三方庫。
- 如果目標可以出網,在目標沒有回顯的時候,可以用來驗證是否存在反序列化漏洞。
漏洞成因
java.net.URL
這個類在進行 equals
比較和 hashCode
計算時,會調用 java.net.InetAddress
類的getByName
方法進行 dns 查詢。
下面是分析所用的測試代碼。
@Test
public void test2(){
try{
URL a = new URL("http://a.62b41v.dnslog.cn");
URL b = new URL("http://b.62b41v.dnslog.cn");
a.equals(b);
URL c = new URL("http://c.62b41v.dnslog.cn");
c.hashCode();
}catch (Exception e){
e.printStackTrace();
}
}
URLStreamHandler
類的 getHostAddress
方法最終就會調用 InetAddress
類的 getByName
方法,所以後面的分析就截止到 getHostAddress
方法。
equals
java 官方文檔說,如果兩個 URL 對象被認爲相等,則必須滿足條件之一就是兩個URL 的主機名可以解析到同一 ip 。
https://docs.oracle.com/javase/7/docs/api/java/net/URL.html#equals(java.lang.Object)
Two hosts are considered equivalent if both host names can be resolved into the same IP addresses;
URL.equals -> URLStreamHandler.equals -> URLStreamHandler.sameFile -> URLStreamHandler.hostsEqual -> URLStreamHandler.getHostAddress
-
入口
-
進入 URL 類的 equals 方法。
-
進入 URLStreamHandler 的 equals 方法。此處判斷條件是兩 ref 相等,並且 sameFile 爲 true。
-
進入 sameFile 中 hosts 比較部分。
-
hosts 比較中就會解析 dns。
hashCode
在計算一個 URL 對象的 hash 時,一個計算因素就是 url 解析的 ip 地址。
URL.hashCode -> URLStreamHandler.hashCode -> URLStreamHandler.getHostAddress
-
入口
-
進入 URL 的 hashCode 方法。
-
調用 URLStreamHandler 的 hashCode 方法,發現其調用 getHostAddress 方法。
ysoserial payload 分析
參考 ysoserial 生成 payload 的框架。可以看到調用的對應類的 getObject 方法。然後序列化這個對象,輸出。
那麼這裏我們就主要關注 URLDNS 這個 payload 類的 getObject 方法。
可以看到它採取的是 hashCode 方式。從某種角度而言,hashCode 的確比 equals 更穩定,後面會探討。
我們先分析簡單的內容。再來分析複雜的。
HashMap ht = new HashMap();
URL u = new URL(null, url, handler);
ht.put(u, url);
通過調試,可以 ht.put
操作看到最終會調用 URL 對象的 hashCode 方法。
一些細節
一. 反序列過程
java 反序列的特性,使得會先創建 HashMap 對象,然後再創建 URL 對象並 put 到 HashMap 中,所以這樣就會觸發 URL 類的 hashCode 方法。
從這個角度來講,我們也可以使用 HashSet 來替代 HashMap 對象。類似類的有很多。
二. 生成時爲什麼不觸發
參考第一條細節,既然如此,那麼在構造 payload 時,也觸發了 URL 的 hashCode 方法,爲什麼此時不會觸發 dns 解析?
爲了不在構造 payload 時觸發,特地自定義了一個繼承 URLStreamHandler 的類,並使用這個類對象構建 URL 對象。
URLStreamHandler handler = new SilentURLStreamHandler();
URL u = new URL(null, url, handler);
在開始就提到過。
URLStreamHandler
類的getHostAddress
方法最終就會調用InetAddress
類的getByName
方法,所以後面的分析就截止到getHostAddress
方法。
所以此處通過重寫 getHostAddress 方法,就不會觸發 dns 解析。
另一個 openConnection 方法是抽象方法,必須實現。
三. 引入自定義類仍可反序列化
接上一條,SilentURLStreamHandler
這個類是我們自定義的類,既然引進了自定義類並用它的實例參與構建對象,那麼按道理來說反序列化應該找不到這個類。那麼序列化爲什麼不報錯呢?
主要是由於傳入的對象最終會被 transient 修飾。構造函數的內容。
此屬性被 transient 修飾。
而 transient 修飾的作用,簡單地說,就是讓被修飾的成員屬性變量不被序列化。這樣,序列化的 URL 對象的 handler 變量就爲 null 。也就不存在與自定義類相關聯的問題。
在反序列化的過程中,會重新創建一個 URLStreamHandler ,以便正常使用功能。
四. 最後爲何將 hashCode 置 -1
Reflections.setFieldValue(u, "hashCode", -1);
這個代碼有什麼作用?
URL 對象 hashCode 方法只有當 hashCode 等於 -1 時纔會計算 hash,纔會觸發域名的 dns 解析。而在創建 payload 時已經計算過值,所以爲了反序列化時再次進行計算觸發就必須將其置爲 -1。
五. 爲何選 hashCode 這條鏈
由漏洞的成因可知,有兩種觸發 dns 解析方法,爲什麼選擇 hashCode ,而不是 equals。
主要是爲了簡單與通用。
hashCode 觸發簡單,包含在 HashMap、HashSet 即可。在構建時只需要注意將第四點,通過反射將 hashCode 重設爲 -1。
而 equals 鏈想要觸發 URL 解析,對 url 有一定要求。可以看到,在走到解析域名那步,需要經過多次比對。