Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    86387969

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

0x01 前言我們通常把反序列化漏洞和反序列化利用鏈分開來看,有反序列化漏洞不一定有反序列化利用鏈(經常用shiro反序列化工具的人一定遇到過一種場景就是找到了key,但是找不到gadget,這也就是在這種場景下沒有可利用的反序列化利用鏈)。如果我們向某個漏洞提交平台提交一個反序列化漏洞,但是不給反序列化利用鏈,那麼平台大概率是不會接受這種漏洞的。

反序列化利用鍊是整個反序列化利用過程中最關鍵的一環,通常反序列化利用鏈需要藉助常用的第三方jar包,其中最有名的就是CommonCollections利用鏈(簡稱CC鏈)。

往期推薦

1、告別腳本小子系列丨JAVA安全(1)——JAVA本地調試和遠程調試技巧

2、告別腳本小子系列丨JAVA安全(2)——JAVA反編譯技巧

3、告別腳本小子系列丨JAVA安全(3)——JAVA反射機制

4、告別腳本小子系列丨JAVA安全(4)——ClassLoader機制與冰蠍Webshell分析

5、告別腳本小子系列丨JAVA安全(5)——序列化與反序列化

0x02反序列化環境準備為了更方便初學者來學習反序列化利用鏈,我們首先要準備當前需要的實驗環境。學習反序列化利用鏈最好的辦法是參考ysoserial(https://github.com/frohoff/ysoserial),ysoserial是一個開源的集成化反序列化利用鏈工具,可以快速生成反序列化利用鏈payload。

如果是不追求細節的小伙伴,可以直接按照參照ysoserial使用文檔來生成payload,如下圖所示。

1656056253110337.png

本文的主要目的是教會小伙伴們告別腳本小子,所以就不直接使用編譯好的jar包,但是我們後面很多利用代碼會參考ysoserial中的代碼,ysoserial中關於反序列化利用鏈的代碼都在ysoserial.payloads包中。

我們的目的是不通過ysoserial框架,自己實現相應的反序列化利用鏈。我們搭建反序列化測試的基礎環境,新建maven項目,添加項目依賴的jar包。

1656056482765667.png

為了模擬序列化和反序列化的過程,編寫兩個公用的靜態方法,這兩個方法將在後面的反序列化利用鏈中被多次使用。其中searialize方法的作用是把對象obj序列化之後保存到文件filename中,unserialize方法的作用是把文件filename中的數據讀出來反序列化為對象。

1656057022119874.png

通過讀寫文件來模擬序列化和反序列化的過程是最直觀的實現方式,但是在實際環境中,我們遇到的都是基於POST輸入的字符輸入流來進行反序列化,如下圖所示。本地讀寫文件的反序列化和基於POST輸入字符的反序列化是完全沒有區別的,所以我們後面在研究反序列化利用鏈的時候都是通過本地文件讀寫的方式來實現,而不會準備專門的WEB漏洞環境。

1656057057835562.png

某系統反序列化漏洞實例

0x03 反序列化利用鏈0x3.1 URLDNS利用鏈URLDNS鍊是JAVA眾多利用鏈中最簡單的一條利用鏈,非常適合初學者研究學習,具有下面的特點:

1)利用鏈只依賴jdk本身提供的類,不依賴其他第三方類,所以具有很高的通用性,可以用於判斷目標是否存在反序列化漏洞。

2)利用鏈本身只能執行域名解析的操作,不能執行系統命令或者其他惡意操作。如果向漏洞提交平台提交反序列化漏洞,但是利用鍊是URLDNS的利用鏈,那麼漏洞提交平台可能會拒絕這個漏洞。

Ysoserial中關於URLDNS鏈的主要代碼如下圖3.1.1,圖3.1.2所示。這裡面有一個很關鍵的點是使用了自定義的SilentURLStreamHandler類,為什麼要使用這個自定義的類,我將在後面說明原因。

1656057124347053.png

圖3.1.1 生成URLDNS利用鏈對象

1656057125681216.png

圖3.1.2 自定義SilentURLStreamHandler類

我們先把ysoserial的代碼搬運到本地,做一名合格的搬運工。直接搬運過來之後運行payload可以查看到DNSLOG的日誌,證明搬運工沒有問題了。搬運過來之後需要去掉ysoserial中的反射調用,我們這裡沒有Reflections類,所以自己寫關於反射調用的代碼,如圖3.1.3所示,運行結果如圖3.1.4所示

1656057492128088.png

圖3.1.3 本地引用URLDNS利用鏈

1656057536210644.png

圖3.1.4 運行之後可以在DNSLOG查看DNS請求日誌

為了理清楚利用鏈的整個流程,我們在java.net. URLStreamHandler類的getHostAddress方法中下斷點調試一下,如圖3.1.5所示。1656057566522110.png

圖3.1.5 下斷點調試URLDNS利用鏈

運行整個payload,根據debug查看棧調用情況,如圖3.1.6所示。其中最關鍵的紅色的框中的部分,可以看出整個利用鏈非常簡單。

1656057604681965.png

圖3.1.6 URLDNS利用鏈的棧調用情況

首先反序列化入口是在HashMap的readObject,在這個方法中會調用本類的hash方法,如圖3.1.7所示。

1656057638166723.png

圖3.1.7 在HashMap的readObject方法中調用HashMap的hash方法

繼續跟進hash方法,在這個方法中會調用key的hashCode方法。 Key對應的值是java.net.URL類的對象,如圖3.1.8所示。

1656057707177043.png

圖3.1.8 通過hash方法調用URL類的hashCode方法

繼續跟進會調用java.net.URL類的hashCode方法,會調用handler字段的hashCode方法。而handler定義來自於URLStreamHandler類,所以會調用URLStreamHandler類的handler方法,如圖3.1.9所示。

1656057735909554.png

圖3.1.9 URL類的hashCode方法調用URLStreamHandler類的hashCode方法

繼續跟進java.net.URLStreamHandler類的hashCode方法,如圖3.1.10所示。這裡可以看出裡面就直接調用了getHostAddress方法,該方法執行之後會進行域名到IP地址的解析請求。

14.png

圖3.1.10 最終調用了域名解析相關的方法getHostAddress方法

到這裡已經看完了整個調用棧的流程,但是還是沒有找到任何理由必須要用自定義的SilentURLStreamHandler類。為了理清楚原因,我們對原來的payload進行修改,去除自定義的SilentURLStreamHandler類,如圖3.1.11所示。

1656057796826844.png

圖3.1.11 修改後的URLDNS利用鏈

還是在java.net. URLStreamHandler類的getHostAddress方法中下斷點。可以看到整個棧調用情況如圖3.1.12所示。可以看出觸發getHostAddress方法的入口點是從generalURLDNS2這個方法,而不是unserialize方法,也就是在生成序列化對象的時候就已經觸發了域名解析的請求。由於JAVA內部對DNS請求存在緩存機制,那麼在反序列化的時候會優先從DNS緩存中查找域名解析記錄,那麼反序列化的時候就收不到正確的DNS請求數據。

1656057822137179.png

圖3.1.12 修改後的URLDNS利用鏈

我們跟一下在生成payload時候觸發getHostAddress的流程,其中最關鍵的是執行HashMap中的put方法,如圖3.1.13所示。

1656057850115774.png

圖3.1.13 URLDNS利用鏈生成對象時調用HashMap的put方法

跟踪put方法的定義,如圖3.1.14所示。裡面會調用hash方法

1656057893998470.png

圖3.1.14 HashMap的put方法會調用本類的hash方法

剩下的流程和上面分析調用棧一樣,最終會導致觸發執行getHostAddress方法。

為了避免在生成payload的時候觸發DNS請求,影響反序列化時DNS請求的執行。有兩種解決辦法。

1)第一種就是像ysoserial代碼中的方式一樣,定義一個類繼承自URLStreamHandler,並且在類中重寫getHostAddress等方法,使得在序列化的時候不會執行getHostAddress方法。

2)第二種辦法是通過通過java.net.URL類中的hashCode方法中的邏輯來避免執行後續的getHostAddress方法。

第一種方法在ysoserial的代碼中已經寫很清楚了,這裡主要再說一下第二種方式。在整個利用鏈中一個很重要的步驟是會調用java.net.URL類中的hashCode方法。如圖3.1.15所示。

1656057973681984.png

圖3.1.15 java.net.URL類中的hashCode方法代碼邏輯

這裡有一個很重要的判斷語句是,如果hashCode不等於-1,則直接返回對應hashCode的值,否則調用hashCode方法計算對應的值。這裡需要特別注意的一點是這裡的hashCode既是java.net.URL類的方法,也是java.net.URL類的字段。所以我們需要

1)第一次put的時候把hashCode字段設置為不是-1的值,避免運行下面的handler.hashCode(this)。

2)生成序列化對象的時候又需要把hashCode字段設置為-1,因為反序列化的時候需要運行下面的handler.hashCode(this)。

所以我們也可以通過反射修改hashCode字段的方式來避免在序列化的時候觸發DNS請求,再次修改後的代碼如圖3.1.16所示。這裡最主要的是增加了通過反射修改hashCode字段的操作。

1656058030110427.png

圖3.1.16 通過控制hashCode字段避免序列化時候觸發DNS請求

這樣之後也能達到和ysoserial代碼一樣的效果。也能正常觸發URLDNS的請求,如圖3.1.17所示。

1656058060623699.png

圖3.1.17 通過修改的payload觸發URLDNS鏈的DNS請求

0x3.2CC鏈CC鍊是最早出現的影響較大的java反序列化利用鏈,原作者一共給出7條利用鏈,但是後來有很多大牛在此基礎上給出了一些改進的利用鏈,對初學者而言,學習CC鍊是屬於學習JAVA反序列化利用鏈的重要基礎。

關於CC鏈的內容很多,限於篇幅有限,這次的文章先開頭對部分CC鏈進行講解,關於CC鏈的更多內容將在下一篇文章中詳述。本次我們主要先講關於CC鏈中的Transform鏈。

在學習Transform鏈之前,首先需要說明JAVA中命令執行的方式。 JAVA中最典型的運行操作系統命令的辦法是通過Runtime類來執行,如圖3.2.1所示。

1656058111625903.png

圖3.2.1 JAVA中執行系統命令的方式

但是在反序列化的過程中不能直接通過Runtime來執行,因為Runtime類沒有繼承Serializable接口,如圖3.2.2所示。

1656058136364167.png

圖3.2.2 Runtime類沒有繼承Serializable接口

但是我們可以通過反射的方式來調用Runtime類執行,並且裡面涉及到的全部類都繼承了Serializable接口,如圖3.2.3所示。這裡有一個坑是,通過反射來執行方法的返回值類型一定是Object類型,需要做強制類型轉換,把Objectl類型轉化為Runtime類型。後續的Transform利用鏈基本上都是通過執行這段代碼來執行系統命令的。

1656058161160001.png

圖3.2.3 通過反射的方式來執行Runtime類的exec方法

嚴格來說Transform鏈屬於整個CC鏈中的Sink點,CC鏈基本上都是通過Transform來最終執行系統命令的。學習Tranform鍊是掌握CC鏈的基礎前提,最典型的Transform鏈如圖3.2.4所示。

1656058185182669.png

圖3.2.4 典型Transform鏈運行情況

直接運行上面的代碼是可以彈出計算器的,多數CC鏈最終也是通過調用類似的代碼來執行系統命令。為了理解Transforml鏈的內容,需要分開來看裡面涉及到的幾個類:InvokerTransformer類、ConstantTransformer類和ChainedTransformer類。

在InvokerTransformer類中,最主要的是transform方法。該方法中通過反射的方式執行任意一個類的方式,如圖3.2.5所示。 Transform執行的方法必須是public修飾符的。

1656058278599022.png

圖3.2.5 通過InvokerTransformer類的transform方法執行任意public方法

InvokerTransformer類的transform方法提供了一種執行任意其他方法的路徑,我們寫一個簡單的例子來幫助大家理解transform方法,如圖3.2.6所示。定義一個類TEST,類中定義一個方法Hello,那麼我們就可以通過tranform方法來執行TEST類的Hello方法。

1656058310116802.png

圖3.2.6 典型的transform方法調用

可能有的小伙伴會疑惑,我要執行Hello,為什麼不直接調用,非要通過transform來調用呢?試想一下,如果我們要執行的是“Runtime.getRuntime()”這樣的方法,但是整個系統中都沒有任何地方直接調用了這個,那麼我們是不是就可以通過transform來間接的執行這個方法呢。

但是現在通過transform來執行方法還有一個很明顯的不足,就是只能執行單個對象的單個方法,不能鍊式調用。形像一點來說明這個問題,我們可以執行”Runtime.getRuntim()”,但是我們不能執行“Runtime.getRuntime().exec(xxxx)”。我們需要找到鍊式調用的方式,幸運的是CommonsCollections中提供了另一個類ChainedTransformer。

ChainedTransformer類中提供了鍊式調用transform方法的辦法,如圖3.2.7所示。 ChainedTransformer類的構造方法是傳入Transformer類型的數組,通過transform方法依次遍歷數組中的每一個元素,上一步的方法調用的輸出作為下一步的方法調用的輸入,完美的鍊式調用解決辦法。