flawopen.com/XML 外部实体(XXE)/Python XML 解析器安全
Python 的 xml.etree.ElementTree 能防止 XXE 吗?
对于经典的文件读取和网络 XXE,Python 内置的 Expat 解析器默认不会访问本地文件,也不会建立网络连接。但这不代表所有 XML 用法都安全:低于 2.7.2 的 Expat 可能遭受实体扩展和长 token 拒绝服务攻击。lxml 的默认值也因版本而异。请固定并显式配置解析器,或用 defusedxml 处理不可信 XML。
风险配置 — 版本和选项会改变暴露面
- XXE(CWE-611):
- 解析器解析攻击者控制的 XML 中外部实体,可能读取本地文件或发起服务器端请求。
- 实体扩展 DoS:
- 嵌套或重复引用在展开时会过度消耗 CPU 或内存。
- Expat:
- Python 内置 XML 模块使用的 C 解析器;用
pyexpat.EXPAT_VERSION检查实际运行版本。 - DTD:
- 文档类型声明可定义实体,并可能根据解析器设置请求外部资源。
不可信 XML 到达解析器
上传文件、SVG、SAML 消息或集成数据越过信任边界并进入 XML API。
解析器实现和选项决定行为
标准库使用 Expat;lxml 使用 libxml2。版本、外部解析、实体扩展和选项决定可触发的行为。
解析或扩展造成影响
若启用外部解析,实体可请求本地或远程内容。易受攻击的扩展或长 token 也可能耗尽资源。
进程权限限制影响范围
文件权限与网络出口限制 XXE 影响;输入大小限制、更新解析器、安全选项和隔离可降低风险。
Python 当前安全指南指出,Expat 默认不访问本地文件或建立网络连接。但低于 2.7.2 的版本可能受 billion-laughs、quadratic-blowup、长 token 或过量内存使用影响。请检查 pyexpat.EXPAT_VERSION,因为 Python 可能使用内置或系统 Expat。这是依版本而异的资源耗尽风险,不代表当前 ElementTree 会解析外部实体。
# Standard-library external access is off by default, # but old Expat builds have resource-exhaustion risks. import xml.etree.ElementTree as ET from xml.parsers import expat print(expat.EXPAT_VERSION) # inspect the runtime parser root = ET.fromstring(untrusted_xml) # lxml before 5.0 defaulted resolve_entities=True. # Explicit unsafe settings illustrate the risky boundary. from lxml import etree parser = etree.XMLParser(resolve_entities=True, load_dtd=True) root = etree.fromstring(untrusted_xml, parser)
from defusedxml.ElementTree import fromstring
# Reject DTD declarations when the format does not require them.
root = fromstring(untrusted_xml, forbid_dtd=True)
# If lxml is required, set its security options explicitly.
from lxml import etree
parser = etree.XMLParser(
resolve_entities=False,
no_network=True,
load_dtd=False,
huge_tree=False,
)
root = etree.fromstring(untrusted_xml, parser)
# Do not run XInclude on untrusted trees without a restricted loader.
为实际部署的解析器选择安全设置
defusedxml.ElementTree 默认禁止实体声明和外部引用;若输入格式不需要 DTD,也设置 forbid_dtd=True。使用 lxml 时显式设置 resolve_entities=False、load_dtd=False、no_network=True 和 huge_tree=False。当前版本默认只解析内部实体;lxml 5.0 之前默认会解析实体。不要对不可信树执行 XInclude,除非加载器受限。解析前限制输入及解压后大小;这些选项无法限制所有资源成本。
- ✓盘点所有 XML 输入点和解析器:ElementTree/Expat、lxml/libxml2、SAX/DOM、SVG、SAML、归档导入,以及后续 XInclude。
- ✓使用受支持的 Python 和 Expat 2.7.2 或更高版本(若可用);用
pyexpat.EXPAT_VERSION检查运行版本,不要假设使用哪份库。 - ✓处理不可信 XML 时使用
defusedxml;若格式不需要 DTD,使用forbid_dtd=True拒绝 DTD。 - ✓使用 lxml 时固定受维护版本,显式禁用实体解析、DTD 加载、网络访问和 huge-tree;不要依赖可能变化的默认值。
- ✓限制压缩前后大小;必要时隔离解析,并在 CI 中用无害的 DTD/实体样本测试,避免发出外部请求。
为什么要根据解析器版本审查默认值
“Python XML”并非单一配置。标准库依赖 Expat,解释器构建可能使用内置或系统库。lxml 包装 libxml2,并在 5.0 版更改实体解析默认值;包装器和显式选项也会改变行为。记录 Python、Expat、lxml、libxml2 版本,并在每个不可信输入点显式设置安全选项。
常见问题
不会。Python 文档说明 Expat 默认不会访问本地文件或网络。检查 Expat 版本,并区分经典 XXE 文件访问与旧版本的资源耗尽问题。
不要跨版本一概而论。当前 XMLParser 默认只解析内部实体、不加载 DTD、不访问网络。lxml 5.0 将 resolve_entities 默认值从 True 改为 internal。请显式设置选项并检查包装器。
不会。它默认禁止实体和外部引用,但 forbid_dtd 默认是 False。格式不需要 DTD 时请设为 True。
不能。限制压缩和解压后的输入大小,更新解析器,避免 huge_tree,也不要用不受限加载器处理不可信 XInclude。