本文首發于“合天智匯”公眾號 作者: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 [email protected]
* @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 [email protected]
* @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反序列漏洞
https://www.hetianlab.com/expc.do?ec=ECID172.19.104.182015111916202700001
(本實驗通過Apache Commons Collections 3為例,分析并復現JAVA反序列化漏洞。)