貌似最近经常看到有Java项目爆出XXE的漏洞并且带有CVE,包括 Spring-data-XMLBean XXE漏洞 、 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漏洞。
我们测试的Payload很简单:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "dnslog-ip">
]>
<evil>&xxe;</evil>
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 如果使用默认配置就会触发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);
同样地,在默认配置下就会存在 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);
在默认情况下会出现 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);
在默认情况下会出现 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
在默认情况下也会出现 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 的修复原理一样就不作说明了。
使用默认的解析方法会存在 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());
使用默认的解析方法会存在 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);
使用默认的解析方法会存在 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));
使用默认的解析方法不会存在 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 最终写出了含有漏洞的代码。
以上。