前言
經過了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:
- 創建對應Handler,設置owner與parent
- 爲Handler添加屬性
- 調用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的繼承關係:
可以看到只改寫了isArgument,而在整個觸發過程中並無影響,所以此處使用void標籤與object標籤完全沒有區別。
Level3:過濾一堆
過濾了object/new/method標籤,void標籤只允許用index,array的class只能用byte,並限制了長度。
CNVD-2018-2725(CVE-2019-2725)最初的poc使用了UnitOfWorkChangeSet這個類,這個類的構造方法如下(從Badcode師傅的Paper裏盜的圖):
最初的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的實現:
Level1/2中由於Object(Void)設置了class屬性,那麼type是有值的,所以直接返回type。
而父類的getContextBean是調用parent的getValueObject函數,來獲取上一級對象,所以此時我們令上一級獲取到的對象爲Class即可,所以此處使用了class標籤令void的上一級獲取的對象爲Class對象。
因爲void標籤只允許使用index屬性,所以此處無法使用method屬性來調用具體函數,所以只能寄期望於構造方法,就有了上面利用UnitOfWorkChangeSet類的構造方法來利用反序列化漏洞的手法。
同樣可利用的類還有之前jackson rce用的FileSystemXmlApplicationContext類。
總結
XMLDecoder的流程還是蠻有意思的,具體各標籤的功能、詳細解析流程,還需要大家自己看一下。
順便重要的事情說三遍:
一定要自己跟一下!
一定要自己跟一下!
一定要自己跟一下!