前言
這幾天繼續在重寫GadgetInspector工具,進一步的增強該自動化工具的source點和sink點,同時增強過程中的漏報和誤報的問題。
這里主要是對其中有關于XXE中的兩點sink進行幾點分析。
sinks
DocumentBuilder類
這個JDK中內置的類是一種的DOM型的解釋器,該種Parser的特點是將完整的xml文檔內容加載到樹結構中去,然后我們通過遍歷結點來獲取我們需要的內容。
首先編寫一段實例代碼,來使用DocumentBuilder類來讀取xml文檔的內容。
Employee.xml
<employees>
<employee id="111">
<firstName>Rakesh</firstName>
<lastName>Mishra</lastName>
<location>Bangalore</location>
</employee>
<employee id="112">
<firstName>John</firstName>
<lastName>Davis</lastName>
<location>Chennai</location>
</employee>
<employee id="113">
<firstName>Rajesh</firstName>
<lastName>Sharma</lastName>
<location>Pune</location>
</employee>
</employees>
DOMParserDemo.java。
public class DOMParserDemo {
public static void main(String[] args) throws Exception{
// 獲取DOM Builder Factory
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 獲取DOM Builder
DocumentBuilder builder = factory.newDocumentBuilder();
// 加載xml文檔
Document document = builder.parse("Employee.xml");
// 建立一個鏈表存放Employee對象
List<Employee> employeeList = new ArrayList<>();
// 提取數據
NodeList nodeList = document.getDocumentElement().getChildNodes();
// 迭代進行讀取xml文檔
for (int i = 0; i < nodeList.getLength(); i++) {
// 獲取對應結點,這里的結點是employee標簽
Node node = nodeList.item(i);
if (node instanceof Element) {
// 創建一個Employee空對象
Employee employee = new Employee();
// 獲取xml文檔中的id值
employee.id = node.getAttributes().getNamedItem("id").getNodeValue();
// 獲取子結點信息
NodeList childNodes = node.getChildNodes();
for (int j = 0; j < childNodes.getLength(); j++) {
// 獲取對應結點
Node node1 = childNodes.item(j);
if (node1 instanceof Element) {
// 獲取對應的信息
String content = node1.getTextContent().trim();
switch (node1.getNodeName()) {
case "firstName":
employee.firstName = content;
break;
case "lastName":
employee.lastName = content;
break;
case "location":
employee.location = content;
break;
}
}
}
// 將獲取的每一個對象添加進入列表中去
employeeList.add(employee);
}
}
// 打印數據
for (Employee employee : employeeList) {
System.out.println(employee);
}
}
}
這種方式就是遍歷xml文檔中的一個一個的文檔來進行對應信息的讀取。

這里只是一個簡單的使用該種解釋器進行xml文檔的讀取。
我們主要是進行XXE的展示。
XXE.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE test [
<!ENTITY xxe SYSTEM "http://127.0.0.1:9999">
]>
<doc>&xxe;</doc>
主要的漏洞點是在調用DocumentBuilder#parse方法進行xml文檔的加載的時候,如果加載的是上面給出的xml內容,將會導致XXE。

簡單的調試了一下。
resolveEntity:95, ChainingEntityResolver (weblogic.xml.jaxp)
resolveEntity:110, EntityResolverWrapper (com.sun.org.apache.xerces.internal.util)
resolveEntityAsPerStax:997, XMLEntityManager (com.sun.org.apache.xerces.internal.impl)
startEntity:1212, XMLEntityManager (com.sun.org.apache.xerces.internal.impl)
scanEntityReference:1908, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
next:3061, XMLDocumentFragmentScannerImpl$FragmentContentDriver (com.sun.org.apache.xerces.internal.impl)
next:602, XMLDocumentScannerImpl (com.sun.org.apache.xerces.internal.impl)
scanDocument:505, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
parse:842, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:771, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:141, XMLParser (com.sun.org.apache.xerces.internal.parsers)
parse:243, DOMParser (com.sun.org.apache.xerces.internal.parsers)
parse:339, DocumentBuilderImpl (com.sun.org.apache.xerces.internal.jaxp)
parse:163, RegistryDocumentBuilder (weblogic.xml.jaxp)
parse:177, DocumentBuilder (javax.xml.parsers)
main:22, DOMParserDemo (study.xmlParser)
在將xml文檔路徑傳入了DocumentBuilder#parser方法之后。

將會將其轉換為InputSource類傳入DocumentBuilderImpl#parser方法中進行解析。

這里將會調用DOMParser#parser繼續進行解析。

按照其中的邏輯將會一次性加載xml文件的所有內容,之后通過調用parse方法進行解析。
最后經過一系列的解析,特別是通過調用XMLDocumentFragmentScannerImpl#scanEntityReference方法的調用來掃描獲取xml文件中的實體,之后方法中調用了XMLEntityManager#startEntity方法進行實體的處理。

在該方法中存在有是否是外部實體的判斷,如果存在有外部實體,將會調用resolveEntityAsPerStax方法來獲取外部實體中的信息,這里將會發起一個請求,也即能達到前面截圖中的效果。
防御方法DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
- 如果業務需要存在有DTD的使用,我們可以轉而求其次,禁用XML中的外部實體和參數實體.
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); //防止外部實體
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); //防止參數實體
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
String FEATURE = null;
FEATURE = "http://javax.xml.XMLConstants/feature/secure-processing";
dbf.setFeature(FEATURE, true);
FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
dbf.setFeature(FEATURE, true);
FEATURE = "http://xml.org/sax/features/external-parameter-entities";
dbf.setFeature(FEATURE, false);
FEATURE = "http://xml.org/sax/features/external-general-entities";
dbf.setFeature(FEATURE, false);
FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
dbf.setFeature(FEATURE, false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
DocumentBuilder builder = dbf.newDocumentBuilder();
builder.parse(xxxx);
SAXParser類
接下來的一個XXE的sink點就是在SAXParser#parser方法的調用中,這種方式的xml文檔的解析,主要是依靠的SAX Parser這個,這個解析器的不同于DOM Parser,這個解釋器是不會直接將完整的XML文檔加載進入內存中,這種解釋器轉而逐行進行XML文檔的解析,當他們遇到不同的元素的時候觸發不同的事件,例如:開始標簽 / 結束標簽 / 字符類似的數據等等事件類型,這種解釋器是一種基于事件的解釋器。
這里也是簡單的舉一個例子,使用的xml文件也是上面已經提到了的Employee.xml文件內容
而對于讀取xml文件的實例。
SAXParserDemo.java
public class SAXParserDemo extends Exception {
public static void main(String[] args) throws Exception{
// 創建SAXParser Factory
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser = factory.newSAXParser();
// 創建自定義的handler對象
SAXHandler saxHandler = new SAXHandler();
// 加載xml文檔,加上自定義的handler對象
saxParser.parse("Employee.xml", saxHandler);
// 打印結果
for (Employee employee : saxHandler.employeeList) {
System.out.println(employee);
}
}
}
class SAXHandler extends DefaultHandler {
// 定義屬性
List<Employee> employeeList = new ArrayList<>();
Employee employee = null;
String content = null;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
switch (qName) {
case "employee":
// 如果遇到了employee標簽采用的邏輯
employee = new Employee();
employee.id = attributes.getValue("id");
break;
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
switch (qName) {
// 遇到結束標簽采用的邏輯
case "firstName":
employee.firstName = content;
break;
case "lastName":
employee.lastName = content;
break;
case "location":
employee.location = content;
break;
case "employee":
employeeList.add(employee);
break;
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
content = String.copyValueOf(ch, start, length).trim();
}
}
這種解釋器的方式,主要是在傳入xml文件路徑的時候,同時傳入了一個handler對象,這個對象是繼承了DefaultHandler這個內置的默認Handler對象的。
在這個類中定義了很多方法。

在查看這個類的結構的時候,其中這個類是實現了幾個接口的,其中實現的有一個接口是ContentHandler,在這個接口中定義了多個和XML文檔內容有關的方法。
- startDocument()
- endElement()
- endDocument()
- startElement()
- ......
有關這些方法的作用,也是很明顯,運行一下子看看效果。

如果我們將XML文檔的地址替換成XXE.xml路徑,同樣將會觸發XXE,同樣簡單的調試了一下。
startElement:36, SAXHandler (study.xmlParser)
startElement:509, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers)
startElement:745, XMLDTDValidator (com.sun.org.apache.xerces.internal.impl.dtd)
scanStartElement:1359, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
scanRootElementHook:1289, XMLDocumentScannerImpl$ContentDriver (com.sun.org.apache.xerces.internal.impl)
next:3132, XMLDocumentFragmentScannerImpl$FragmentContentDriver (com.sun.org.apache.xerces.internal.impl)
next:852, XMLDocumentScannerImpl$PrologDriver (com.sun.org.apache.xerces.internal.impl)
next:602, XMLDocumentScannerImpl (com.sun.org.apache.xerces.internal.impl)
scanDocument:505, XMLDocumentFragmentScannerImpl (com.sun.org.apache.xerces.internal.impl)
parse:842, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:771, XML11Configuration (com.sun.org.apache.xerces.internal.parsers)
parse:141, XMLParser (com.sun.org.apache.xerces.internal.parsers)
parse:1213, AbstractSAXParser (com.sun.org.apache.xerces.internal.parsers)
parse:643, SAXParserImpl$JAXPSAXParser (com.sun.org.apache.xerces.internal.jaxp)
parse:392, SAXParser (javax.xml.parsers)
parse:274, SAXParser (javax.xml.parsers)
main:20, SAXParserDemo (study.xmlParser)
對于這種解釋器,和上面的一種不同在于,前面主要是調用的DOMParser類進行解析,而這里主要是調用了SAXParser類進行解析。
在對應的parse方法中。

傳入了兩個參數,一個是XML文檔的路徑,一個是自己實現的繼承了DefaultHandler類的類對象
在將XML文檔路徑封裝成了InputSource對象之后進行解析的邏輯。

這里不同于前面的,這里在創建了一個XMLReader對象之后,設定了對應的Handler對象。
獨特的點體現在調用XMLDocumentFragmentScannerImpl#scanDocument方法進行XML的掃描的過程中
在掃描到ROOT Element位置的時候,將會觸發對應的Hook點。

具體點就是調用了scanRootElementHook方法。


這里是配置了對應的Handler的,這里將會在AbstractSAXParser#startElement方法的調用中,調用其中的ContentHandler的對象方法進行處理。

防御方法。
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
SAXParser parser = spf.newSAXParser();
parser.parse(InputSource, (HandlerBase) null);