1. 漏洞概述
XXE (XML External Entity Injection,XML 外部实体注入) 是一种针对解析 XML 数据的应用程序的攻击。
当允许 XML 包含外部实体(External Entity),且 XML 解析器没有被安全配置时,攻击者可以构造恶意的 XML 内容发送给服务器。服务器在解析这个 XML 时,会根据实体定义去读取本地文件、或者向外部发起网络请求。
XXE 漏洞的危害非常大,常被用于:
- 任意文件读取(读取服务器上的密码文件、配置文件、源代码等)。
- SSRF (服务端请求伪造)(探测内网端口、攻击内网其他应用)。
- DoS (拒绝服务攻击)。
2. 闯关实操 (Exploitation)
第一步:分析输入点
打开关卡页面,提示这是一个“接收 xml 数据的 api”,并提供了一个文本输入框。既然明确说明了接收 XML 数据,我们直接尝试构造包含恶意外部实体的 XML Payload。
第二步:构造 Payload 并注入
根据 XML DTD(文档类型定义)的语法,我们定义一个名为 xxe 的外部实体,利用 SYSTEM 关键字结合 file:// 伪协议,指示解析器去读取 Linux 系统的敏感文件 /etc/passwd。
我们输入的完整 Payload 如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<user>&xxe;</user>
语法解析:
<!DOCTYPE foo [...]>:定义文档类型。<!ENTITY xxe SYSTEM "file:///etc/passwd">:声明一个名为xxe的外部实体,它的内容是读取/etc/passwd文件的结果。<user>&xxe;</user>:在 XML 元素中调用刚才定义的实体&xxe;。
第三步:成功利用
点击提交后,页面成功回显了 <pre> 标签包裹的内容,里面赫然是服务器 root:x:0:0:root... 等账号信息!这证明后端的 XML 解析器成功解析了我们的外部实体,并将文件内容替换到了 &xxe; 的位置输出了出来。

3. 源码分析 (Code Review)
为什么这个 API 会存在 XXE 漏洞?我们来看一下后端的 PHP 源码:
$html='';
// 考虑到目前很多版本里面 libxml 的版本都>=2.9.0 了, 所以这里添加了 LIBXML_NOENT 参数开启了外部实体解析
if(isset($_POST['submit']) and $_POST['xml'] != null){$xml =$_POST['xml'];
// 核心解析函数
$data = @simplexml_load_string($xml,'SimpleXMLElement',LIBXML_NOENT);
if($data){$html.="<pre>{$data}</pre>";
}else{$html.="<p>XML 声明、DTD 文档类型定义、文档元素这些都搞懂了吗?</p>";
}
}
漏洞成因与技术细节:
- 直接解析用户输入:程序直接接收了
$_POST['xml'],没有做任何危险字符过滤,直接丢给了simplexml_load_string()函数进行解析。 - 强制开启外部实体解析(画重点):
在 PHP 环境中,底层的 XML 解析库是libxml。自libxml2.9.0 版本起,默认已经禁用了外部实体的解析 ,也就是说,现代 PHP 环境默认是免疫 XXE 的。
但是,靶场作者为了让我们能够复现漏洞,特意在函数中加入了第三个参数LIBXML_NOENT。这个参数的作用恰恰是:将 XML 中的实体引用(如&xxe;)替换成实体声明的值。正是这个不安全的配置参数,导致了 XXE 漏洞的产生。 - 直接回显结果:解析成功后,直接将包含敏感文件内容的
$data拼接到$html中输出。
4. 修复建议
防御 XXE 漏洞的核心思想非常简单:禁止 XML 解析器解析任何外部实体。
对于 PHP 语言而言,修复方案如下:
- 移除不安全的配置参数
如果 PHP 的libxml版本 >= 2.9.0,最直接的修复方法就是在调用解析函数(如simplexml_load_string,DOMDocument::loadXML)时,不要 传入LIBXML_NOENT这个常量。 - 显式禁用外部实体 (通用方案)
在解析 XML 数据之前,显式调用 PHP 提供的函数来禁用外部实体的加载(适用于较老版本的 PHP):// 修复示例 // 在解析 XML 前,强制禁用外部实体的加载 libxml_disable_entity_loader(true); $xml = $_POST['xml']; // 移除 LIBXML_NOENT 参数 $data = @simplexml_load_string($xml, 'SimpleXMLElement');(注:libxml_disable_entity_loader()函数在 PHP 8.0 中已被废弃,因为在 PHP 8+ 中底层 libxml 版本较高,已经默认安全。) - 输入过滤:对传入的 XML 字符串进行过滤,检查是否包含
<!DOCTYPE和<!ENTITY等关键字(作为辅助防御手段,不推荐作为主要手段)。