Ysoserial URLDNS鏈分析


Ysoserial URLDNS鏈分析

文章首發安全客:https://www.anquanke.com/post/id/248004

0x00 寫在前面

Java提供了一種序列化的機制可以將一個Java對象進行序列化后,用一個字節序列表示,並在Java虛擬機之間或網絡間傳輸,之后可通過readObject()方法反序列化將字節序列反序列化還原成原先的對象。而如果對於反序列化沒有做安全限制,並且反序列化點可控的話,我們可以通過一個惡意的序列化對象通過利用鏈,去達到執行任意代碼的目的。

而我們平常復現或利用反序列化漏洞時,也經常會用到ysoserial項目去生成序列化payload,下面就先分析學習一下ysoserial中的URLDNS鏈。

0x01 運行ysoserial

下載ysoserial項目源碼,導入IDEA

打開pom.xml文件,是個Maven項目,點擊刷新拉取缺少的依賴

拉取完可能還會有些爆紅的地方,但是不會影響程序運行。

下面尋找下程序的入口點,搜索mainclass即可

0x02 序列化payload生成流程

先來大致看下這個GeneratePayload的main方法。

首先進行一個判斷,是否傳入了兩個參數,如果不是則打印幫助信息;是的話會依次分別賦值給payloadType和command變量。

之后實例化了一個需要繼承ObjectPayload類的類實例化對象,跟進一下getPayloadClass方法,看看是如何實現的

在ysoserial.payloads.ObjectPayload.Utils下

可以看到會先反射一個class對象,之后獲取包名和類名並加上我們之前傳入的payloadType參數並將返回值賦值給GeneratePayload的payloadclass。

后續生成一個payloadclass所表示的類的新實例化對象賦值給payload,之后調用getObject方法,跟進看一下getObject方法

該方法定義於ObjectPayload接口,繼續跟進

發現跳到了URLDNS,且該類實現了ObjectPayload接口。

那么上面的操作就是根據輸入的參數,獲取類名並定位到該類中,也就是進入到利用鏈的類文件中,調用該類的getObject方法生成payload並將返回值(也就是序列化payload)賦值給object對象。

URLDNS中的操作我們放到后面再分析,繼續回到GeneratePayload#main方法中

之后調用serialize()方法將payload序列化並打印輸出。

那么上面將大致的payload生成流程過了一遍,下面跟進去運行測試一下,生成一個URLDNS鏈的序列化payload。

這里沒設置參數,所以可以看到出現了幫助信息,我們設置下參數

點擊Edit Configurations,如下圖設置參數再重新運行,這里可以隨便寫個地址,或者獲取一個dnslog用於測試

這次就成功運行了,下面的亂碼數據就是上面分析過的,經過一系列操作生成的URLDNS序列化之后的payload字節序列

0x03 URLDNS鏈使用

URLDNS鏈的利用效果是發起一次遠程請求,而不能去執行命令。基本上是用來測試是否存在反序列化漏洞的一個鏈,比如在一些無法回顯執行命令的時候,可以通過URLDNS鏈去發送一個dns解析請求,如果dnslog收到了請求,就證明了存在漏洞。

下面命令行使用下URLDNS鏈看看效果

將生成的序列化payload保存在一個txt里,后面用一個測試demo讀取文件數據再給他反序列化一下,觀察dnslog請求就可以了。

java -jar ysoserial.jar URLDNS "http://ba0i6v.dnslog.cn" 
java -jar ysoserial.jar URLDNS "http://ba0i6v.dnslog.cn" > URLDNS.txt

反序列化測試demo

import java.io.*;

public class URLDNSTestDemo {
    public static void main(String[] args) throws Exception {

        FileInputStream fis = new FileInputStream("/Users/b/Desktop/ScanTools/URLDNS.txt");
        ObjectInputStream bit = new ObjectInputStream(fis);
        bit.readObject();
    }
}

運行demo測試是否可以成功將序列化的payload執行並在dnslog留下記錄

dnslog平台已經接收到了請求,說明我們利用測試成功了。

下面我們分析一下URLDNS鏈執行流程是怎樣的。

0x04 URLDNS鏈分析

URLDNS這條利用鏈並不依賴於第三方的類,而是JDK中內置的一些類和方法。所以並不需要一些Java的第三方類庫就可以達到目的。

打開URLDNS源碼,URLDNS.java

在最上面的注釋中,作者已經給出了URLDNS的Gadget chain

 *   Gadget Chain:
 *     HashMap.readObject()
 *       HashMap.putVal()
 *         HashMap.hash()
 *           URL.hashCode()

下面debug看一下是如何執行的。

觸發點在put方法,我們在put方法處打一個斷點,在Edit configurations中設置好參數,debug mainclass文件即可

成功在斷點處停下,下面我們一步一步調試分析

首先我們看下這個ht對象的類HashMap,可以觀察到有序列化接口

那么全局搜一下readObject方法,發現在最后調用了putVal方法進行了一個hash計算

回到斷點處跟進一下putVal方法,參數的keyvalue都是我們輸入的dnslog地址

繼續跟進,看一下hash方法

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

這里因為傳入的key不為0,會執行key.hashcode方法,繼續跟一下hashcode方法

public synchronized int hashCode() {
        if (hashCode != -1)
            return hashCode;

        hashCode = handler.hashCode(this);
        return hashCode;
    }

這的handlerURLStreamHandler的對象,這里在序列化時將hashcode值設-1,所以直接調用了handlerhashCode方法並重新賦值返回了hash,繼續跟進

這里調用的是URLStreamHandler這個類的hashCode方法,此時我們傳入的URL u對象其實就是我們的dnslog地址,后面通過u.getProtocol()方法獲取了協議名字,也就是http

繼續跟進,下面會進行一個if判斷,執行語句后 h的值會加上portocal的hashCode()方法的返回值,跟一下這里調用的hashCode()方法

發現是String的hashcode()方法,這里做了一頓操作得到String的hash值並賦值給h並返回出來

繼續跟進,調用了getHostAdress方法

來看一下getHostAdress是如何實現的,觀察邏輯,這里會先后調用getHost()getByName()兩個方法,最終是通過getByName(getHost())這樣去發送的dnslog請求

到了這里,dnslog就已經可以接收到請求信息了,致此這歌利用鏈差不多就分析完了。

整個調用鏈為

HashMap.readObject() => HashMap.putVal() => HashMap.hash() => URL.hashCode() => 
URLStreamHandler.hashCode() => URLStreamHandler.hashCode().getHostAddress => URLStreamHandler.hashCode().getHostAddress.InetAddress.getByName()

0x05 URLDNS鏈中的一些細節

回到URLDNS類,可以看到還有一些其他的點其實並沒有去深入分析,比如下面這幾段代碼

...
  
URLStreamHandler handler = new SilentURLStreamHandler();

...
  
static class SilentURLStreamHandler extends URLStreamHandler {

                protected URLConnection openConnection(URL u) throws IOException {
                        return null;
                }

                protected synchronized InetAddress getHostAddress(URL u) {
                        return null;
                }
        }
...
Reflections.setFieldValue(u, "hashCode", -1);

這里new的是一個SilentURLStreamHandler對象,繼承自URLStreamHandler並且重寫了兩個方法,分別是getHostAddress和openConnection且都是空方法,比較耐人尋味。

關於重寫getHostAddress方法與hashcode = -1

Java中繼承子類的同名方法會覆蓋父類的方法,所以說本來是要執行URLStreamHandler中的getHostAddress方法會變成執行他的子類SilentURLStreamHandler的getHostAddress方法(也就是空方法)。因為在默認情況下hashcode的值就是-1,那么在本地進行將 URL 對象 put 進 hashMap時如果hashcode值為-1就直接調用了getHostAddress方法也就會在生成序列化payload的時候在本地發送一次dnslog請求,所以ysoserial中給出對應的解決方法就是讓SilentURLStreamHandler繼承URLStreamHandler並重寫getHostAddress為空方法,這樣即使hashcode為-1也不會在本地發送請求(繼承子類的同名方法會覆蓋父類的方法)

Reflections.setFieldValue(u, "hashCode", -1);最后通過反射將hashcode重新設置為-1,那么在反序列化過程就會觸發dnslog請求

關於handler

在調試的時候其實就發現了一個特殊的關鍵字transient,不可序列化,也就是說handler最終不會在我們產生的序列化payload中,所以即便SilentURLStreamHandler重寫了父類方法,也不會影響反序列化的過程

關於重寫openConnection方法

這個就比較簡單了,因為URLStreamHandler是一個抽象類,所以實現類必須實現父類的所有抽象方法

0x06 結尾

根本原因還是在HashMap存在反序列化點並重寫了readObject方法可以接受反序列化數據,之后因為hashcode=-1,通過putVal計算hash時一步步的調用直到調用getHostAddress方法,就會發起遠程請求觸發dnslog。整體下來其實並不算太復雜,只是需要耐心一點一點調試,弄清楚邏輯和一些方法的作用就可以了

文章有分析的不對的地方還請師傅們指出

參考文章:

https://www.secpulse.com/archives/157407.html

https://www.cnblogs.com/nice0e3/p/13772184.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM