XML炸弹本质是利用XML解析器对实体的递归展开机制,以几百字节输入触发GB级内存消耗;Python xml.etree.ElementTree默认未禁用DTD和外部实体,易受Billion Laughs等攻击;防御需主动禁用DOCTYPE、使用defusedxml等安全替代库,并杜绝日志回显原始XML。

XML炸弹本质:用几百字节触发GB级内存爆炸
XML炸弹不是传统意义上的“恶意代码”,而是一种精心构造的、语法完全合法的XML文档,它利用XML解析器对实体(entity)的递归展开机制,在极小输入下引发指数级资源消耗。一个典型例子展开后能吃掉3GB内存——攻击者只发几百字节,服务器就可能瞬间卡死或崩溃。
为什么xml.etree.ElementTree默认会中招
Python标准库的xml.etree.ElementTree在fromstring()或parse()时,若未显式禁用DTD和外部实体,就会处理中定义的嵌套实体。而Billion Laughs正是靠这种“宏展开”实现指数增长:
]> &c;
这里&c;展开为5×5=25个lol,再深一层就是125个——5层嵌套就能生成超过3亿个字符串副本。
- 默认行为:
ElementTree不校验也不限制实体展开深度或总展开长度 - 风险场景:处理用户上传的XML、接收SOAP请求、解析第三方配置文件
- 关键误区:以为“没用
lxml就安全”——标准库同样脆弱
Java/PHP/NET里怎么防:三步硬性配置
所有主流语言的XML解析器都提供禁用机制,但必须主动开启,不能依赖默认值:
-
Java(SAX/DOM):设置
setFeature("http://apache.org/xml/features/disallow-doctype-decl", true),或使用DocumentBuilderFactory.setFeature("http://javax.xml.XMLConstants/feature/secure-processing", true) -
PHP(libxml):调用
libxml_disable_entity_loader(true)(注意:该函数在PHP 8.0+已废弃,需改用LIBXML_NOENT | LIBXML_DTDATTR组合并手动过滤) -
.NET(System.Xml):将
XmlReaderSettings.DtdProcessing设为DtdProcessing.Prohibit,且XmlResolver = null
漏掉任意一项,XXE或XML炸弹都可能绕过防护。
真正有效的防御不是“堵”,而是“换”
禁用DTD只是底线,不是终点。生产环境应优先切换到更安全的替代方案:
- 用
defusedxml(Python)替代原生xml.etree:from defusedxml.ElementTree import parse tree = parse(file_obj) # 自动禁用所有危险特性
- 用
xmlreader(PHP)替代simplexml_load_string,配合XMLReader::open()+setParserProperty(XMLReader::SUBST_ENTITIES, false) - 拒绝解析含
/code>的XML——很多业务根本不需要DTD,直接在入口层丢弃即可
最常被忽略的一点:日志、监控、调试接口如果会把原始XML回显或存入日志,哪怕你解析器本身是安全的,攻击者也能通过报错信息反推服务器路径或触发盲注外带——XML安全从来不只是解析器的事。










