作者:fnmsd
來源:https://blog.csdn.net/fnmsd/article/details/89889144

前言

經過了Weblogic的幾個XMLDecoder相關的CVE(CVE-2017-3506、CVE-2017-10352、CVE-2019-2725),好好看了一下XMLDecoder的分析流程。

本文以jdk7版本的XMLDecoder進行分析,jdk6的XMLDecoder流程都寫在了一個類里面(com.sun.beans.ObjectHandler)

此處只分析XMLDecoder的解析流程,具體Weblogic的漏洞請看其它幾位師傅寫的Paper。

WebLogic RCE(CVE-2019-2725)漏洞之旅-Badcode

Weblogic CVE-2019-2725 分析報告-廖新喜

不喜歡看代碼的可以看官方關于XMLDecoder的文檔: Long Term Persistence of JavaBeans Components: XML Schema

XMLDecoder的幾個關鍵類

XMLDecoder的整體解析過程是基于Java自帶的SAX XML解析進行的。

以下所有類都在com.sun.beans.decoder包中

DocumentHandler

DocumentHandler繼承自DefaultHandler,DefaultHandler是使用SAX進行XML解析的默認Handler,所以Weblogic在對XML對象進行validate的時候也使用了SAX,保證過程的一致性。

DefaultHandler實現了EntityResolver, DTDHandler, ContentHandler, ErrorHandler四個接口。

DocumentHandler主要改寫了ContentHandler中的幾個接口,畢竟主要是針對內容進行解析的,其它的保留默認就好。

ElementHandler及相關繼承類

XMLDecoder對每種支持的標簽都實現了一個繼承與ElementHandler的類,具體可以在DocumentHandler的構造函數中看到:

所以XMLDecoder只能使用如上標簽。

其中繼承關系與函數重寫關系如下(很大,可以看大圖或者自己用idea生成再看):

如上的繼承關系也是object標簽可以用void標簽替代用的原因,后面詳說。

ValueObject及其相關繼承類

ValueObject是一個包裝類接口,包裹了實際解析過程中產生的對象(包括null)

繼承關系:

一般的對像由ValueObjectImpl進行包裹,而null\true\false(非boolean標簽)則直接由自身Handler進行代表,實現相關接口。

XMLDecoder過程中的幾個關鍵函數

DocumentHandler的XML解析相關函數的詳細內容可以參考Java Sax的ContentHandler的文檔

ElementHandler相關函數可以參考ElementHandler的文檔

DocumentHandler創建各個標簽對應的ElementHandler并進行調用。

startElement

處理開始標簽,包括屬性的添加 DocumentHandler:。XML解析處理過程中參數包含命名空間URL、標簽名、完整標簽名、屬性列表。根據完整標簽名創建對應的ElementHandler并添加相關屬性,繼續調用其startElement。

ElementHandler: 除了array標簽以外,都無操作。

endElement

結束標簽處理函數 DocumentHandler: 調用對應ElementHandler的endElement函數,并將當前ElementHandler回溯到上一級的ElementHandler。

ElementHandler: 沒看有重寫的,都是調用抽象類ElementHandler的endElement函數,判斷是否需要向parent寫入參數和是否需要注冊標簽對象ID。

characters

DocumentHandler: 標簽包裹的文本內容處理函數,比如處理<string>java.lang.ProcessBuilder</string>包裹的文本內容就會從這個函數走。函數中最終調用了對應ElementHandler的addCharacter函數。

addCharacter

ElementHandler: ElementHandler里的addCharacter只接受接種空白字符(空格\n\t\r),其余的會拋異常,而StringElementHandler中則進行了重寫,會記錄完整的字符串值。

addAttribute

ElementHandler: 添加屬性,每種標簽支持的相應的屬性,出現其余屬性會報錯。

getContextBean

ElementHandler: 獲取操作對象,比如method標簽在執行方法時,要從獲取上級object/void/new標簽Handler所創建的對象。該方法一般會觸發上一級的getValueObject方法。

getValueObject

ElementHandler: 獲取當前標簽所產生的對象對應的ValueObject實例。具體實現需要看每個ElementHandler類。

isArgument

ElementHandler: 判斷是否為上一級標簽Handler的參數。

addArgument

ElementHandler: 為當前級標簽Handler添加參數。

XMLDecoder相關的其它

兩個成員變量,在類的實例化之前,通過對parent的調用進行增加參數。

parent

最外層標簽的ElementHandler的parent為null,而后依次為上一級標簽對應的ElementHandler。

owner

ElementHandler: 固定owner為所屬DocumentHandler對象。

DocumentHandler: owner固定為所屬XMLDecoder對象。

簡易版解析流程圖

PPT畫的:-D

跟著漏洞來波跟蹤(Weblogic)

來一份簡單的代碼:

public static void main(String[] args) throws FileNotFoundException {
    String filename = "1.xml";
    XMLDecoder XD =new XMLDecoder(new FileInputStream(filename));
    Object o = XD.readObject();
    System.out.println(o);
}

Level1:什么過濾都沒有

<java>
    <object class="java.lang.ProcessBuilder">
        <array class="java.lang.String" length="1">
            <void index="0">
                <string>calc</string>
            </void>
        </array>
        <void method="start"/>
    </object>
</java>

首先看下DocumentHandler的startElement: 1. 創建對應Handler,設置owner與parent 2. 為Handler添加屬性 3. 調用Handler的startElement

(后面DocumentHandler的部分忽略,直接從ElementHandler開始) 下面從object標簽對應的ObjectElementHandler開始看: 進入obejct標簽,object標簽帶有class屬性,進入: 可以看到判斷的列表里沒有class標簽,會調用父類(NewElementHandler)的addAttribute方法。 給type賦值為java.lang.ProcessBuilder對應的Class對象。

中間創建array參數的部分略過,有興趣的同學可以自己跟一下。

進入void標簽,設置好method參數,由于繼承關系,看上面那張addAttribute圖就好。

退出void標簽,進入elementHandler的endElement函數:

由于繼承關系,調用NewElementHandler的getValueObject函數:

繼續進入進入ObjectElementHandler的帶參數getValueObject函數: 此處的getContextBean會調用上一級也就是Object標簽的getValueObject來獲取操作對象。

略過中間步驟,再次進入ObjectElementHandler的getValueObject方法:

最終通過Expression創建了對象: (可以看出此處的Expression的首個參數是來自于上面getContextBean獲取的Class對象,先記住,后面會用)

再次回到Void標簽對應的getValueObject函數: 最終通過Expression調用了start函數:

如果對繼承關系感覺比較蒙的話,可以看下一節的繼承關系圖。

PS: 雖然ObjectElementHandler繼承自NewElementHandler,但是其重寫了getValueObject函數,兩者是使用不同方法創建類的實例的。 再PS: 其實不加java標簽也能用,但是沒法包含多個對象了。

Level2:只過濾了object標簽

把上面的object標簽替換為void即可。

VoidElementHandler的繼承關系:

VoidElementHandler

可以看到只改寫了isArgument,而在整個觸發過程中并無影響,所以此處使用void標簽與object標簽完全沒有區別。

Level3:過濾一堆

過濾了object/new/method標簽,void標簽只允許用index,array的class只能用byte,并限制了長度。

CNVD-2018-2725(CVE-2019-2725)最初的poc使用了UnitOfWorkChangeSet這個類,這個類的構造方法如下(從Badcode師傅的Paper里盜的圖):

UnitOfWorkChangeSet_Constructor

最初的poc主要利用UnitOfWorkChangeSet類在構造函數中,會將輸入的byte數組的內容進行反序列化,所以說剛開始說是反序列化漏洞。

其實這個洞是利用了存在問題的類的構造函數,因為沒法用調用method了,就取了這種比較折中的方法。(其實還是有部分方法可以調用的:-D)。

在做這個實驗時需要導入weblogic 10.3.6的modules目錄下com.oracle.toplink_1.1.0.0_11-1-1-6-0.jar文件。

<java>
    <class><string>oracle.toplink.internal.sessions.UnitOfWorkChangeSet</string><void>
            <array class="byte" length="2">
                <void index="0">
                    <byte>-84</byte>
                </void>
                <void index="0">
                    <byte>-19</byte>
                </void>
            </array>
        </void>
    </class>
</java> 

由于class標簽繼承了繼承了string標簽的addCharacter函數,導致其會將標簽中包裹的空白字符(空格\r\n\t)也加入到classname中,導致找class失敗,所以至少要將\<class>到\<void>之間的空白字符刪除。

PS: 其實這里不加string標簽也沒問題。

Level1中說到:

Expression的首個參數是來自于上面getContextBean獲取的Class對象

也就是說,如果能夠找到替代上面object/void+class屬性的方法令getContextBean可以獲取到Class對象,也久可以調用構造函數進行對象的創建。

我們來看下此處調用的getContextBean的實現: UnitOfWorkChangeSet_Constructor

Level1/2中由于Object(Void)設置了class屬性,那么type是有值的,所以直接返回type。

而父類的getContextBean是調用parent的getValueObject函數,來獲取上一級對象,所以此時我們令上一級獲取到的對象為Class即可,所以此處使用了class標簽令void的上一級獲取的對象為Class對象。

因為void標簽只允許使用index屬性,所以此處無法使用method屬性來調用具體函數,所以只能寄期望于構造方法,就有了上面利用UnitOfWorkChangeSet類的構造方法來利用反序列化漏洞的手法。

同樣可利用的類還有之前jackson rce用的FileSystemXmlApplicationContext類。

總結

XMLDecoder的流程還是蠻有意思的,具體各標簽的功能、詳細解析流程,還需要大家自己看一下。

順便重要的事情說三遍: 一定要自己跟一下! 一定要自己跟一下! 一定要自己跟一下!


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/916/