本文首發於“合天智匯”公眾號 作者:Fortheone
前言
最近學習java反序列化學到了weblogic部分,weblogic之前的兩個反序列化漏洞不涉及T3協議之類的,只是涉及到了XMLDecoder反序列化導致漏洞,但是網上大部分的文章都只講到了觸發XMLDecoder部分就結束了,並沒有講為什么XMLDecoder會觸發反序列化導致命令執行。於是帶着好奇的我就跟着調了一下XMLDecoder的反序列化過程。
xml序列化
首先了解一下java中的XMLDecoder是什么。XMLDecoder就是jdk中一個用於處理xml數據的類,先看兩個例子。
這里引用一下淺藍表哥的(強推淺藍表哥的博客https://b1ue.cn/
import java.beans.XMLEncoder; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; /** * @author 淺藍 * @email blue@ixsec.org * @since 2019/4/24 12:09 */ public class Test { public static void main(String[] args) throws IOException, InterruptedException { HashMap<Object, Object> map = new HashMap<>(); map.put("123","aaaa"); map.put("321",new ArrayList<>()); XMLEncoder xmlEncoder = new XMLEncoder(System.out); xmlEncoder.writeObject(map); xmlEncoder.close(); } }

這樣就把map對象變成了xml數據,再使用XMLDecoder解析一下。
/** * @author 淺藍 * @email blue@ixsec.org * @since 2019/4/24 12:09 */ public class Test { public static void main(String[] args) throws IOException, InterruptedException { String s = "<java version=\"1.8.0_131\" class=\"java.beans.XMLDecoder\">\n" + " <object class=\"java.util.HashMap\">\n" + " <void method=\"put\">\n" + " <string>123</string>\n" + " <string>aaaa</string>\n" + " </void>\n" + " <void method=\"put\">\n" + " <string>321</string>\n" + " <object class=\"java.util.ArrayList\"/>\n" + " </void>\n" + " </object>\n" + "</java>"; StringBufferInputStream stringBufferInputStream = new StringBufferInputStream(s); XMLDecoder xmlDecoder = new XMLDecoder(stringBufferInputStream); Object o = xmlDecoder.readObject(); System.out.println(o); } }

就可以把之前的xml數據反序列化回map對象,那么如果對xml數據進行修改,使其變成一個執行命令的數據。比如說:
<java version="1.7.0_80" class="java.beans.XMLDecoder"> <object class="java.lang.ProcessBuilder"> <array class="java.lang.String" length="1"> <void index="0"><string>calc</string></void> </array> <void method="start"></void> </object> </java>
然后對其反序列化即可執行命令彈出計算器。

現在我們知道了如果使用XMLDecoder去反序列化xml數據,數據中包含的命令會被執行。接下來就對其進行分析一下。
XMLDecoder反序列化漏洞成因
一、XML數據解析前的函數處理

在readObject處打上斷點開始debug

進入了parsingComplete方法,跟進。

其中使用XMLDecoder的handler屬性DocumentHandler的parse方法,並且傳入了我們輸入的xml數據,跟進。

這里調用了SAXParserImpl類的parse方法。

然后又進了xmlReader的parse方法。

這里又調用了xmlReader父類AbstractSAXParser的parser方法。

最后進入了XML11Configuration類的parse方法。
二、XML數據的處理

在XML11Configuration中進行了很多解析XML之前的操作,我們不去仔細研究,看到處理XML數據的函數scanDocument。跟進查看

這個函數通過迭代的方式對XML數據的標簽進行解析,網上有些文章寫道“解析至END_ELEMENT時跟進調試”,但是我看了一下我這里的END_ELEMENT。

里面沒有函數可以跟進啊,然后搜了一些其他的文章,是因為jdk版本的問題,處理的邏輯放在了next函數里。在do while循環里跳了大概十次,就開始解析了xml的標簽。

跳到XMLDocumentScannerImpl中的next方法

跳到XMLDocumentFragmentScannerImpl中的next方法,解析到endtag時會走到scanEndElement方法里。
然后就到了網上說的endElement方法里,跟進。

這一部分的解析可以參考下圖:

也就是說解析時會按照標簽一個一個解析。

這里調用了DocumentHandler的endElement方法。接下來就是很重要的部分


這里的handler是StringElementHandler,但是這個類沒有重寫endElement方法,所以調用的是父類ElementHandler的endElement方法,其中調用了getValueObject來獲取標簽中的value值,這里的標簽是string標簽,所以獲取到的值是calc。


然后將其添加到其父類標簽VoidElementHandler的Argument屬性中。

然后將handler指向其父類VoidElementHandler。

繼續解析到void標簽,此時的handler就是VoidElementHandler,接着調用getValueObject。但是因為沒有重寫該方法,所以調用父類NewElementHandler的getValueObject。


繼續跟進發現實現了反射調用invoke方法,也就是執行了set方法。接着再解析Array標簽,按照上面的步驟解析,就完成了這一部分參數的解析。
<array class="java.lang.String"length="1"> <void index="0"> <string>calc</string> </void> </array>

那么再按照上面的步驟解析object標簽,然后調用new 方法實例化 ProcessBuilder類。

然后解析到void標簽獲取到start方法,然后通過調用start方法實現了命令執行,彈出計算器。
也就相當於最后拼接了 new java.lang.ProcessBuilder(new String[]{"calc"}).start();

文章有說的不對的地方請師傅們指點,剛開始學java,大佬們輕噴。。。
參考文章
https://b1ue.cn/archives/239.html
https://zhuanlan.zhihu.com/p/108754274
https://blog.csdn.net/SKI_12/article/details/85058040
相關實驗
Java反序列漏洞
(本實驗通過Apache Commons Collections 3為例,分析並復現JAVA反序列化漏洞。)