* 前不久,@图南在分析WxJava的XXE注入漏洞时,发现是由于开发者错误使用了Java官方一个容易被误解的API,导致之前的修复方案失效。我和南哥交流之后,经过简单的跟踪分析,发现归根结底是Feature的问题,所以就带大家一起来填填这个坑吧。请在阅读本文之前好好的把南哥的文章看一遍,其中涉及到的内容我都不会再占篇幅描述了。
在我之前的《XXE注入漏洞概述》一文中,以dom4j的SAXParser
为例简单分析了它对XML文档的解析过程,虽然DocumentBuilderFactory
使用的是DOMParser
,但是很荣幸,它俩都会调用XMLDocumentFragmentScannerImpl.scanDocument()
扫描XML文档。而且OWASP XXE Prevention Cheat Sheet项目中也表示它们的修复方案是一致的:
DocumentBuilderFactory
,SAXParserFactory
andDOM4J
XML Parsers can be configured using the same techniques to protect them against XXE.
所以我们在这稍微简化一下分析过程,不再描述DocumentBuilderFactory
的完整解析过程,为大家减减负,直接挑重点说。
这应该是OWASP最推荐的防御方案了,它与LOAD_EXTERNAL_DTD
一起在XMLDocumentScannerImpl
中被组成为一组RECOGNIZED_FEATURES
,主要过程如下:
- 当
DocumentBuilderFactory.setFeature()
将http://apache.org/xml/features/disallow-doctype-decl
设置为true
时,DOMParser
会改变内部fConfiguration
变量中相应键的值,以及XMLNSDocumentScannerImpl
中fDisallowDoctype
变量的值 - 在调用
DOMParser.parse()
时,将XML11Configuration
中XMLDocumentScannerImpl
的fDisallowDoctype
也重置为true
XMLDocumentFragmentScannerImpl.scanDocument()
由PrologDriver.next()
依次扫描到<
、!
字符后进入SCANNER_STATE_DOCTYPE
阶段,该阶段第一件事就是判断fDisallowDoctype
的值,如果为true
,则直接报告异常信息中断扫描
由此可知,为什么OWASP会这样描述这个方案:
This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all XML entity attacks are prevented
这次我们先看OWASP对它的描述:
If for some reason support for inline DOCTYPEs are a requirement, then ensure the entity settings are disabled and beware that SSRF attacks and denial of service attacks are a risk.
简单的说,如果你需要支持内联DOCTYPE,可以使用setExpandEntityReferences()
,但要注意SSRF和DoS风险。
那么我们就来分析一下它为什么没有效果:
DocumentBuilderFactory.setExpandEntityReferences()
传入false
会改变DocumentBuilderFactoryImpl
中expandEntityRef
变量的值 (默认为true
)DocumentBuilderFactory.newDocumentBuilder()
创建DocumentBuilderImpl
时,会根据expandEntityRef
的 相反值 改变fConfiguration
中http://apache.org/xml/features/dom/create-entity-ref-nodes
的值XMLParser.reset()
时,AbstractDOMParser
中fCreateEntityRefNodes
变量也被重置为true
- 在
XMLDocumentFragmentScannerImpl.scanDocument()
时,调用scanDoctypeDecl()
扫描DOCTYPE,之后交给DTDDriver.next()
处理实体声明 - 当进入
START_ELEMENT
阶段后,startEntity()
会调用scanEntityReference()
扫描并解析实体引用&xxe;
,而在endEntity()
判断fCreateEntityRefNodes
为false
时,将会移除掉该节点
由此可知,setExpandEntityReferences()
的意思其实是实体引用的值解析后,是否仍在Dom树中保留其原始的表示引用的节点。设置为false
的不展开代表保留,则会创建一个对应节点存放在Dom树中。
因此,如果是DTD扫描阶段的SSRF之类的攻击,无论setExpandEntityReferences()
传入何值,都是不起任何防御作用的。
在和南哥一起填这个坑的时候,我们发现网络上还是有不少关于XXE攻防相关的文章的修复建议中只提到了setExpandEntityReferences(false)
这个方案,有被Java官方误导过的同学都赶紧更正一下吧。