XXE漏洞本质是XML解析器无条件加载攻击者控制的外部实体,需禁用DOCTYPE声明及所有外部实体相关特性。Java中须组合设置多个setFeature为false并禁用XInclude;Python lxml需显式设resolve_entities=False和no_network=True;最彻底防御是在解析前过滤或拒绝含DOCTYPE的请求。

XXE漏洞本质是XML解析器加载了恶意外部实体
XXE(XML External Entity)不是XML语法错误,而是解析器在处理声明时,无条件加载了攻击者控制的SYSTEM或PUBLIC外部实体。典型后果包括读取本地文件(如/etc/passwd)、发起内网SSRF、触发DNS外带、甚至导致拒绝服务。
Java中使用DocumentBuilder时必须禁用外部DTD和实体
默认的DocumentBuilderFactory开启外部实体解析,且setFeature("http://apache.org/xml/features/disallow-doctype-decl", true)在老版本JDK(如JDK 6/7)上不生效。真正有效的组合是:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
-
disallow-doctype-decl应设为true,但仅靠它不够——某些老解析器仍会尝试解析已存在的DOCTYPE -
external-general-entities和external-parameter-entities必须显式设为false,否则ENTITY声明仍可被解析 -
load-external-dtd设为false防止加载外部DTD文件,哪怕DOCTYPE存在
Python中lxml和xml.etree.ElementTree的差异很关键
xml.etree.ElementTree默认不解析外部实体,相对安全;但lxml(尤其搭配etree.XMLParser())默认启用,并支持resolve_entities=True(默认值)。必须显式关闭:
from lxml import etree
parser = etree.XMLParser(
resolve_entities=False,
no_network=True,
dtd_validation=False,
load_dtd=False
)
tree = etree.parse("input.xml", parser)
-
resolve_entities=False是核心开关,漏掉它就等于没防 -
no_network=True阻止解析器访问网络资源(如http://attacker.com/evil.dtd) - 即使传入
remove_comments=True或remove_pis=True,也不影响XXE防护,别混淆关注点 - 若用
etree.fromstring(),必须传入同一parser实例,不能依赖全局默认
所有场景下都该禁用DOCTYPE声明本身
最彻底的防御不是“解析但不加载外部实体”,而是根本不让DOCTYPE进入解析流程。多数现代XML解析器支持“无DOCTYPE模式”或预过滤:
- 对不可信输入,在解析前用正则粗筛:
re.sub(r'', '', xml_str, flags=re.DOTALL)+re.sub(r'^>]*>', '', xml_str)(注意:仅作辅助,不能替代解析器配置) - Spring Framework中,
@RequestBody绑定XML时,应配置Jaxb2RootElementHttpMessageConverter并设置setSupportDtd(false) - 若业务根本不需要DOCTYPE(如纯数据交换),在API层直接拒绝含
/code>的请求体,HTTP 400响应并记录日志
很多团队只关了external-entities,却忘了DOCTYPE本身可能触发解析器初始化逻辑,或暴露内部路径信息。真正安全的起点,是让恶意声明根本进不了解析器。










