Pikachu靶场通关笔记(42) — XXE漏洞 (XML External Entity)

24次阅读
没有评论

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>

语法解析:

  1. <!DOCTYPE foo [...]>:定义文档类型。
  2. <!ENTITY xxe SYSTEM "file:///etc/passwd">:声明一个名为 xxe 的外部实体,它的内容是读取 /etc/passwd 文件的结果。
  3. <user>&xxe;</user>:在 XML 元素中调用刚才定义的实体 &xxe;

第三步:成功利用

点击提交后,页面成功回显了 <pre> 标签包裹的内容,里面赫然是服务器 root:x:0:0:root... 等账号信息!这证明后端的 XML 解析器成功解析了我们的外部实体,并将文件内容替换到了 &xxe; 的位置输出了出来。

Pikachu 靶场通关笔记(42) — XXE 漏洞 (XML External Entity)

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>";
    }
}

漏洞成因与技术细节:

  1. 直接解析用户输入:程序直接接收了 $_POST['xml'],没有做任何危险字符过滤,直接丢给了 simplexml_load_string() 函数进行解析。
  2. 强制开启外部实体解析(画重点)
    在 PHP 环境中,底层的 XML 解析库是 libxmllibxml 2.9.0 版本起,默认已经禁用了外部实体的解析 ,也就是说,现代 PHP 环境默认是免疫 XXE 的。
    但是,靶场作者为了让我们能够复现漏洞,特意在函数中加入了第三个参数 LIBXML_NOENT。这个参数的作用恰恰是:将 XML 中的实体引用(如 &xxe;)替换成实体声明的值。正是这个不安全的配置参数,导致了 XXE 漏洞的产生。
  3. 直接回显结果:解析成功后,直接将包含敏感文件内容的 $data 拼接到 $html 中输出。

4. 修复建议

防御 XXE 漏洞的核心思想非常简单:禁止 XML 解析器解析任何外部实体。

对于 PHP 语言而言,修复方案如下:

  1. 移除不安全的配置参数
    如果 PHP 的 libxml 版本 >= 2.9.0,最直接的修复方法就是在调用解析函数(如 simplexml_load_string, DOMDocument::loadXML)时,不要 传入 LIBXML_NOENT 这个常量。
  2. 显式禁用外部实体 (通用方案)
    在解析 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 版本较高,已经默认安全。)
  3. 输入过滤:对传入的 XML 字符串进行过滤,检查是否包含 <!DOCTYPE<!ENTITY 等关键字(作为辅助防御手段,不推荐作为主要手段)。
正文完
 0
评论(没有评论)