基本概念
XMLDecoder用於將XMLEncoder創建的xml文檔內容反序列化爲一個Java對象,其位於java.beans包下。
影響版本
XMLDecoder在JDK 1.4~JDK 11中都存在反序列化漏洞安全風險。
Demo
import com.sun.beans.decoder.DocumentHandler;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.beans.XMLDecoder;
public class test {
public static void XMLDecode_Deserialize(String path) throws Exception {
File file = new File(path);
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
XMLDecoder xd = new XMLDecoder(bis);
xd.readObject();
xd.close();
}
public static void main(String[] args){
//XMLDecode Deserialize Test
String path = "poc.xml";
try {
XMLDecode_Deserialize(path);
// File f = new File(path);
// SAXParserFactory sf = SAXParserFactory.newInstance();
// SAXParser sp = sf.newSAXParser();
//
// DefaultHandler dh = new DefaultHandler();
// DocumentHandler dh = new DocumentHandler();
// sp.parse(f, dh);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Payload:
poc.xml,可以看到java標籤的class屬性指定XMLDecoder類,對象標籤指定ProcessBuilder類、void標籤指定方法爲start,即可調用ProcessBuilder.start()來執行其中的命令。
<?xml version="1.0" encoding="UTF-8"?>
<java version="1.8.0_131" 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" />
</object>
</java>
運行後,會彈出計算器:
調試分析
在IDEA下設置斷點跟蹤調試。
在readObject()處設置斷點,可看到XMLDecoder對象xd的input屬性包含了輸入XML文檔的路徑:
繼續往裏調試,調用到XMLDecoder.parsingComplete()時,發現裏面調用了XMLDecoder.this.handler.parse(),其中this.handler即爲DocumentHandler,換句話說,就是調用了DocumentHandler.parser()來解析輸入的XML文檔內容:
跟蹤進去,可以看到DocumentHandler.parser()中調用了SAXParserFactory.newInstance().newSAXParser().parse()來解析XML內容:
接着設置xmlReader的相關handler,如處理XML內容、實體、錯誤、文檔類型定義、文件等句柄,最後調用xmlReader.parse()解析XML文件內容:
繼續調試,在XML11Configuration.parse()中發現調用determineDocVersion():
跟蹤進去發現,determineDocVersion()主要獲取XML實體掃描器然後掃描解析<?xml version=…?>來獲取XML文檔的版本信息:
返回版本信息後,繼續往下在XML11Configuration.parse()中調用startDocumentParsing()函數,主要是重置掃描器的版本配置並開始文件掃描準備,其中開始文件掃描準備是調用startEntity()函數(跟蹤進去可以看到是通知掃描器開始實體掃描,其中文檔實體的僞名稱爲“[xml]”、DTD的僞名稱爲“[dtd]”、參數實體名稱以“%”開頭;接着函數內部會調用startDocument()函數開始準備文件掃描):
可以看到最後調用到的startDocument()函數會清空當前對象和句柄爲文件掃描的開始做準備:
返回到XML11Configuration.parse()中繼續往下調試,調用scanDocument()開始文件掃描:
進入scanDocument(),可以看到設置實體句柄後,主要是執行do while循環體,其中的包含START_DOCUMENT、START_ELEMENT、CHARACTERS、SPACE、ENTITY_REFERENCE、PROCESSING_INSTRUCTION、COMMENT、DTD、CDATA、NOTATION_DECLARATION、ENTITY_DECLARATION、NAMESPACE、ATTRIBUTE、END_ELEMENT等的掃描識別:
中間XML節點解析的過程不用過多分析,調試至END_ELEMENT時,可以看到其中提取出“calc”參數值:
跟蹤進去後面的getValueObject()函數,可以看到變量var3和var4,分別爲獲取到ProcessBuilder類名和start方法名,在調用Expression():
繼續跟蹤到裏面,最後會調用MethodUtil.invoke()方法實現反射執行任意類方法:
再次F7直接執行了代碼彈出計算器:
整體地看一下,整個調用過程大致如下:
XMLDecoder.readObject() -> XMLDecoder.parsingCompelete() -> DocumentHandler.parse() -> SAXParserFactory.newInstance().newSAXParser().parse() -> xmlReader.parse()
可以發現,XMLDecoder類解析XML是調用DocumentHandler類實現的,而DocumentHandler類是基於SAXParser類對XML的解析上的。
那麼可以去分析一下,到底哪個類纔是真正的漏洞類。測試一下,可以看出DocumentHandler類纔是XMLDecoder反序列化漏洞的根源類:
檢測方法
全局搜索XMLDecoder關鍵字,排查是否調用readObject()函數且參數是否可控。
防禦方法
若可以儘量不使用XMLDecoder反序列化XML內容;若使用則儘量確保參數不可由外界輸入,儘量以白名單的方式限定XML文檔名且結合嚴格的過濾機制。