JAVA常見的XXE漏洞寫法和防禦
貌似最近經常看到有Java專案爆出XXE的漏洞並且帶有CVE,包括 ofollow,noindex" target="_blank">Spring-data-XMLBean XXE漏洞 、 3GGPCIc7FZaubUTh18Q" target="_blank" rel="nofollow,noindex">JavaMelody元件XXE漏洞解析 、 Apache OFBiz漏洞 。微信支付SDK的XXE漏洞。本質上xxe的漏洞都是因為對xml解析時允許引用外部實體,從而導致讀取任意檔案、探測內網埠、攻擊內網網站、發起DoS拒絕服務攻擊、執行系統命令等。
apache OFBiz中的XML解析是由 UtilXml.java 中 readXmlDocument()
完成的:
public static Document readXmlDocument(InputStream is, boolean validate, String docDescription) throws SAXException, ParserConfigurationException, java.io.IOException { //omit java code Document document = null; /* Standard JAXP (mostly), but doesn't seem to be doing XML Schema validation, so making sure that is on... */ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(validate); factory.setNamespaceAware(true); factory.setAttribute("http://xml.org/sax/features/validation", validate); factory.setAttribute("http://apache.org/xml/features/validation/schema", validate); factory.setFeature("http://xml.org/sax/features/external-general-entities", false); factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); factory.setXIncludeAware(false); factory.setExpandEntityReferences(false);
我們就有理由相信XXE漏洞是由 DocumentBuilderFactory
設定不當操作造成的,當然我們現在看到的是修改之後的版本;
JavaMelody
中是由 PayloadNameRequestWrapper.java 中的 parseSoapMethodName
來解析XML。
private static String parseSoapMethodName(InputStream stream, String charEncoding) { try { // newInstance() et pas newFactory() pour java 1.5 (issue 367) final XMLInputFactory factory = XMLInputFactory.newInstance(); final XMLStreamReader xmlReader; if (charEncoding != null) { xmlReader = factory.createXMLStreamReader(stream, charEncoding); } else { xmlReader = factory.createXMLStreamReader(stream); } // omit java code }
根據 JavaMelody元件XXE漏洞解析 的分析,是由於 xmlReader
沒有限制外部查詢導致的XXE漏洞。
同樣地,微信支付SDK的XXE漏洞和Spring-data-XMLBean XXE漏洞都是是使用了 DocumentBuilderFactory
沒有限制外部查詢而導致XXE。
從這些例子中,可以發現在Java中其實存在著非常多的解析XML的庫,同時由於在Java應用中會大量地使用到XML,因此就會出現使用不同的庫對XML繼續解析,而編寫這些程式碼的研發人員並沒有相關的安全背景,所以就導致了層出不窮地Java XXE漏洞。
不同庫的Java XXE漏洞
我們測試的Payload很簡單:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE root [ <!ENTITY xxe SYSTEM "dnslog-ip"> ]> <evil>&xxe;</evil>
DocumentBuilderFactory
錯誤地修復方式
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = dbf.newDocumentBuilder(); 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); // 讀取xml檔案內容 FileInputStream fis = new FileInputStream("path/to/xxexml"); InputSource is = new InputSource(fis); builder.parse(is);
看似設定得很很全面,但是直接仍然會被攻擊,原因就是在於 DocumentBuilder builder = dbf.newDocumentBuilder();
這行程式碼需要在 dbf.setFeature()
之後才能夠生效;
正確地修復方式
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(); // 讀取xml檔案內容 FileInputStream fis = new FileInputStream("path/to/xxexml"); InputSource is = new InputSource(fis); Document doc = builder.parse(is);
注意 DocumentBuilder builder = dbf.newDocumentBuilder();
在兩種不同的位置的差異性。
SAXBuilder
這個庫貌似使用得不是很多。 SAXBuilder
如果使用預設配置就會觸發XXE漏洞;如下
SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(InputSource);
修復方法
方式1
SAXBuilder builder = new SAXBuilder(true); Document doc = builder.build(InputSource);
方式2
SAXBuilder builder = new SAXBuilder(); builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); builder.setFeature("http://xml.org/sax/features/external-general-entities", false); builder.setFeature("http://xml.org/sax/features/external-parameter-entities", false); builder.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); Document doc = builder.build(InputSource);
SAXParserFactory
同樣地,在預設配置下就會存在 XXE
漏洞。
SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser parser = spf.newSAXParser(); parser.parse(InputSource, (HandlerBase) null);
修復方法
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);
SAXReader
在預設情況下會出現 XXE
漏洞。
SAXReader saxReader = new SAXReader(); saxReader.read(InputSource);
修復方法
SAXReader saxReader = new SAXReader(); saxReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); saxReader.setFeature("http://xml.org/sax/features/external-general-entities", false); saxReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false); saxReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); saxReader.read(InputSource);
SAXTransformerFactory
在預設情況下會出現 XXE
漏洞
SAXTransformerFactory sf = (SAXTransformerFactory) SAXTransformerFactory.newInstance(); StreamSource source = new StreamSource(InputSource); sf.newTransformerHandler(source);
但是有趣的是,在預設配置,雖然能夠觸發XXE漏洞,但是出現執行時會報錯;如下所示:

但是隻是存在Web的解析記錄。
修復方法
SAXTransformerFactory sf = (SAXTransformerFactory) SAXTransformerFactory.newInstance(); sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); StreamSource source = new StreamSource(InputSource); sf.newTransformerHandler(source);
通過跟蹤原始碼發現, XMLConstants.ACCESS_EXTERNAL_DTD
的內容是 http://javax.xml.XMLConstants/property/accessExternalDTD
, XMLConstants.ACCESS_EXTERNAL_STYLESHEET
是 http://javax.xml.XMLConstants/property/accessExternalStylesheet
SchemaFactory
在預設情況下也會出現 XXE
漏洞。
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema"); StreamSource source = new StreamSource(ResourceUtils.getPoc1()); Schema schema = factory.newSchema(InputSource);
和 SAXTransformerFactory
雖然在執行時會報錯,當時仍然能夠觸發 XXE
。
同樣也只存在Web的解析記錄。
修復方法
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema"); factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); StreamSource source = new StreamSource(InputSource); Schema schema = factory.newSchema(source);
和 SAXTransformerFactory
的修復原理一樣就不作說明了。
TransformerFactory
使用預設的解析方法會存在 XXE
問題。
TransformerFactory tf = TransformerFactory.newInstance(); StreamSource source = new StreamSource(InputSource); tf.newTransformer().transform(source, new DOMResult());
修復方法
TransformerFactory tf = TransformerFactory.newInstance(); tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); StreamSource source = new StreamSourceInputSource); tf.newTransformer().transform(source, new DOMResult());
ValidatorSample
使用預設的解析方法會存在 XXE
問題
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema"); Schema schema = factory.newSchema(); Validator validator = schema.newValidator(); StreamSource source = new StreamSource(InputSource); validator.validate(source);
修復方法
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema"); Schema schema = factory.newSchema(); Validator validator = schema.newValidator(); validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); StreamSource source = new StreamSource(InputSource); validator.validate(source);
XMLReader
使用預設的解析方法會存在 XXE
問題
XMLReader reader = XMLReaderFactory.createXMLReader(); reader.parse(new InputSource(InputSource));
修復方法
XMLReader reader = XMLReaderFactory.createXMLReader(); reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); reader.setFeature("http://xml.org/sax/features/external-general-entities", false); reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false); reader.parse(new InputSource(InputSource));
Unmarshaller
使用預設的解析方法不會存在 XXE
問題,這也是唯一一個使用預設的解析方法不會存在 XXE
的一個庫。
Class tClass = Some.class; JAXBContext context = JAXBContext.newInstance(tClass); Unmarshaller um = context.createUnmarshaller(); Object o = um.unmarshal(ResourceUtils.getPoc1()); tClass.cast(o);
總結
其實,通過對不同的XML解析庫的修復方式可以發現, XXE
的防護值需要限制帶外實體的注入就可以了,修復方式也簡單,需要設定幾個選項為發 false
即可,可能少許的幾個庫可能還需要設定一些其他的配置,但是都是類似的。
總體來說修復方式都是通過設定feature的方式來防禦 XXE
。兩種方法分別是:
"http://apache.org/xml/features/disallow-doctype-decl", true "http://apache.org/xml/features/nonvalidating/load-external-dtd", false "http://xml.org/sax/features/external-general-entities", false "http://xml.org/sax/features/external-parameter-entities", false
配置如上。
另外一種是:
XMLConstants.ACCESS_EXTERNAL_DTD, "" XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""
本質上 XXE
的問題就是一個配置不當的問題,即容易發現也容易防禦,但是前提是需要知道有這個漏洞,這也是就是很多開發人員因為不知道 XXE
最終寫出了含有漏洞的程式碼。
以上。