PHP文件包含漏洞实战:从LFI到伪协议利用与防御
1. 项目概述:一次典型的PHP文件包含漏洞实战
最近在复盘一些经典的Web安全实战场景,发现PHP文件包含漏洞(File Inclusion Vulnerability)依然是许多CTF比赛和渗透测试中的高频考点,也是理解Web应用安全逻辑的一个绝佳切入点。这次我们就来深度拆解一个名为“巧用PHP文件包含漏洞获取flag.php”的实战项目。这个标题听起来就很直接,目标明确:利用一个存在文件包含漏洞的PHP应用,最终读取到服务器上名为 flag.php 的关键文件。 flag 通常是CTF比赛中的“旗帜”,里面藏着通关所需的密钥或答案,而在真实渗透测试中,它可能代表着敏感配置文件、数据库凭证或其他关键信息。
对于刚接触Web安全的朋友来说,文件包含漏洞可能有点抽象。简单类比一下,它就像是一个本应只允许你查看“公共书架”上指定书籍的图书馆管理员(Web应用),但因为管理员的指令(代码逻辑)有缺陷,你不仅能让他拿公共书架的书,还能通过一些特殊的“暗号”(比如路径遍历或伪协议),让他去拿“馆长办公室保险柜”(服务器其他目录)里的机密文件,甚至让他去执行你递给他的一张写着指令的纸条(远程文件包含执行代码)。这个漏洞的危害性极大,因为它可能直接导致敏感信息泄露、网站被挂马,甚至整个服务器被控制。
本篇文章,我将以一个模拟的实战环境为背景,带你一步步拆解如何发现、确认并最终利用一个PHP本地文件包含(LFI)漏洞,并进阶到利用PHP伪协议来读取源代码,最终拿到 flag.php 中的内容。整个过程,我会穿插讲解背后的原理、每一步的操作意图、可能遇到的坑以及我个人的一些排查技巧。无论你是正在学习安全的学生,还是希望加固自己应用的开发者,相信都能从中获得实实在在的收获。
2. 漏洞原理与核心环境搭建
2.1 PHP文件包含漏洞的本质
要利用漏洞,首先得明白它为什么会产生。PHP中提供了四个用于文件包含的函数: include 、 require 、 include_once 、 require_once 。它们的本意是提高代码复用性,比如把数据库连接配置、页头页尾等公共部分写成单独的文件,然后在需要的地方包含进来。漏洞产生的根本原因在于,开发者 将用户可控的输入,未经充分过滤就直接拼接到了这些包含函数的参数中 。
举个例子,一个典型的漏洞代码可能长这样:
<?php
$page = $_GET['page']; // 直接接收用户输入的参数
include($page . '.php'); // 拼接后缀后直接包含
?>
这段代码的初衷可能是让用户通过 ?page=home 来访问 home.php 页面。但如果攻击者传入 ?page=../../../../etc/passwd ,经过拼接后,服务器尝试包含的文件就变成了 ../../../../etc/passwd.php 。如果服务器没有对路径进行限制,攻击者通过 ../ 进行目录遍历,就可能读取到系统敏感文件。更危险的是,如果应用在部署时错误配置,没有强制添加 .php 后缀,或者攻击者利用空字节截断(在PHP旧版本中)等方式,就能直接包含 /etc/passwd 这样的系统文件。
本地文件包含(LFI)和远程文件包含(RFI)是它的两种主要形式。LFI只能包含服务器本地的文件,而RFI则可以通过 http:// 或 ftp:// 等协议包含远程服务器上的文件,这通常意味着可以直接执行远程的恶意代码,危害等级更高。现代PHP默认配置通常关闭了 allow_url_include 选项,因此RFI较少见,但LFI依然广泛存在且威力不容小觑。
2.2 靶场环境搭建与初步探测
为了实战,我们需要一个存在漏洞的环境。这里我推荐使用DVWA(Damn Vulnerable Web Application)或专门的文件包含漏洞靶场。以DVWA为例,将其部署在本地(例如使用PHPStudy、XAMPP或Docker),并将安全级别设置为“Low”。
访问文件包含漏洞模块,我们通常会看到一个简单的页面,可能有一个文件选择器或链接,URL类似 http://target/vuln.php?page=file1.php 。我们的第一步就是信息收集:
- 参数探测 :尝试修改
page参数的值,观察页面变化。例如,尝试?page=file2,看是否会自动添加后缀;尝试?page=../../../../etc/passwd,看是否会报错或输出系统信息。 - 错误信息分析 :PHP的错误信息是宝藏。如果开启了
display_errors,错误的文件路径会直接暴露在页面上,这能帮助我们确认漏洞是否存在,并了解服务器的部分目录结构。 - 后缀测试 :尝试不带后缀、带不同后缀(
.php,.inc,.txt,.log)的输入,观察服务端的处理逻辑。
在我的这次实战模拟中,目标URL是 http://192.168.1.100/vuln.php?page=include.php 。将参数改为 ?page=../../../../etc/passwd 后,页面返回了一个Warning,提示 include.php 文件不存在,但并未显示遍历路径的错误。这暗示了服务端可能对输入添加了固定的后缀(如 .php ),导致我们传入的路径被拼接后变成了一个不存在的 .php 文件。这是一个重要的线索,意味着我们需要找到方法“绕过”这个后缀限制。
注意 :在实际测试中,务必在授权范围内进行。未经授权的测试是违法的。使用本地靶场或获得明确授权的演练环境是唯一正确的方式。
3. 核心利用链:从LFI到源码读取
3.1 利用PHP伪协议绕过限制
当直接路径遍历读取 /etc/passwd 失败时,我们就要祭出PHP文件包含漏洞的“神器”——PHP伪协议(PHP Wrappers)。伪协议允许我们以流的方式访问各种资源,在文件包含的语境下,最常用的是 php://filter 和 php://input 。
php://filter 协议 是我们读取PHP文件源代码的关键。因为正常情况下,服务器包含一个 .php 文件时会执行它,我们看不到源代码。但 php://filter 可以让我们在包含文件前,先对文件流进行一层“过滤”处理,比如将其内容进行Base64编码。这样,服务器执行的不是PHP代码,而是经过编码后的文本,我们拿到编码文本后再解码,就能获得源代码。
其基本格式为: php://filter/convert.base64-encode/resource=目标文件路径
现在,结合我们之前发现的“自动添加 .php 后缀”的情况,构造Payload: ?page=php://filter/convert.base64-encode/resource=flag 。服务器接收到这个参数后,会拼接成 php://filter/convert.base64-encode/resource=flag.php ,然后去包含这个“资源”。 resource= 后面的 flag 会被当作文件名,并加上 .php 后缀,最终指向我们想读取的 flag.php 文件。 convert.base64-encode 过滤器会将这个文件的内容进行Base64编码后再输出。
发送这个Payload后,页面没有显示正常的网页内容,而是输出了一串看似乱码的Base64字符串,例如 PD9waHAgCiRmbGFnID0gImN0ZntUaGlzX0lTX0FfRkxBR30iOwpAZW5kPw== 。这是一个强烈的成功信号!
3.2 解码与Flag提取
拿到Base64编码的字符串后,我们需要将其解码。可以在本地使用Python、在线解码工具或Burp Suite的Decoder模块进行解码。将上面的字符串解码后,我们得到:
<?php
$flag = "ctf{This_IS_A_FLAG}";
@end?>
Bingo!我们成功读取到了 flag.php 的源代码,并从中获取到了Flag: ctf{This_IS_A_FLAG} 。这个过程清晰地展示了如何将一次看似被后缀限制的本地文件包含,通过伪协议转化为源代码读取漏洞。
这里有一个关键技巧 :为什么用Base64编码?因为PHP文件可能包含各种特殊字符(如 < ? > ),直接输出可能会被浏览器解释为HTML标签或触发其他处理,导致显示不全或错乱。Base64编码能将任何二进制数据转化为纯ASCII字符,确保内容完整、无损地传输。除了 base64-encode , read 、 string.rot13 等过滤器也可能在特定场景下有用,但Base64是最通用可靠的。
4. 漏洞利用的深入与扩展
4.1 利用日志文件进行RCE(条件苛刻但值得了解)
如果目标服务器不仅存在LFI,我们还能找到写入特定内容的日志文件,就有可能将LFI升级为远程代码执行(RCE)。这是一种更高级的技巧,成功率依赖于环境配置。
常见利用点 :
- Web访问日志 :如Apache的
/var/log/apache2/access.log或Nginx的/var/log/nginx/access.log。当我们访问网站时,浏览器发送的HTTP请求头(如User-Agent)会被记录到日志中。如果我们能控制User-Agent,并将其设置为一段PHP代码(如<?php system($_GET[‘c’]);?>),那么这段代码就会被原样写入日志文件。 - 其他用户输入日志 :如一些应用的自定义错误日志,可能记录了包含未过滤参数的URL。
利用步骤 :
- 确认日志路径 :通过LFI尝试包含常见的日志路径,或根据错误信息推测。
- 污染日志 :使用Burp Suite或Curl,发送一个特殊的HTTP请求,将
User-Agent设置为PHP代码。curl -H “User-Agent: <?php system(\$_GET[‘cmd’]);?>” http://target/ - 包含日志文件 :通过LFI漏洞去包含这个日志文件,例如
?page=../../../var/log/apache2/access.log。由于日志文件中包含了我们注入的PHP代码,服务器在包含时会将其当作PHP代码执行。 - 执行命令 :现在,我们可以在包含日志文件的URL后面附加参数来执行命令,例如
?page=../../../var/log/apache2/access.log&cmd=id,服务器就会执行id命令并将结果返回。
重要注意事项 :这种方法在实际中面临诸多挑战。现代服务器日志可能对特殊字符进行转义,PHP代码无法原样写入;日志文件权限可能不允许Web用户读取;日志文件体积巨大,包含可能导致超时或内存耗尽。因此,这通常作为在非常理想化的靶场环境或老旧系统上的拓展思路,而非首选方案。
4.2 利用PHP临时文件与包含
另一种更隐秘的思路涉及PHP处理文件上传时产生的临时文件。当用户上传文件时,PHP会先将文件保存到一个临时的位置(如 /tmp/phpXXXXXX ),然后在脚本执行结束后删除。如果存在LFI漏洞,且我们能精确预测或爆破出这个临时文件名(时间窗口极短),理论上可以包含它。但这需要精确的时间竞争攻击(Race Condition),难度极高,在实际渗透中几乎不可行,仅在部分CTF极端题目中出现。
5. 防御策略与安全开发建议
作为开发者,了解漏洞如何利用,是为了更好地防御它。以下是一些根本性的防御措施:
-
白名单校验 :这是最有效的方法。不要使用用户输入直接作为包含的文件名。应该预先定义好允许包含的文件列表(白名单),用户输入只能选择白名单中的项。
$allowed_pages = [‘home.php’, ‘about.php’, ‘contact.php’]; $page = $_GET[‘page’]; if (in_array($page, $allowed_pages)) { include($page); } else { include(‘error.php’); } -
固定目录与后缀 :如果必须动态包含,应将包含文件限制在某个特定目录下,并强制添加后缀。
$base_dir = ‘/var/www/html/includes/’; $page = basename($_GET[‘page’]); // 使用basename去除路径 $file = $base_dir . $page . ‘.php’; if (file_exists($file) && is_file($file)) { include($file); } -
避免动态包含 :重新审视架构,是否真的需要动态文件包含?很多情况下,使用路由分发(如
index.php?action=home然后根据action调用对应函数或类)是更安全的选择。 -
安全配置 :
- 在生产环境中关闭
display_errors,防止路径信息泄露。 - 将
open_basedir配置项设置为Web根目录,限制PHP可访问的文件系统范围。 - 确保
allow_url_include设置为Off(默认值),彻底杜绝RFI的可能性。
- 在生产环境中关闭
-
代码审计与自动化扫描 :在开发流程中引入安全代码审计和自动化漏洞扫描工具,可以有效识别出潜在的文件包含漏洞点。
6. 实战排查与常见问题记录
在实际利用过程中,你可能会遇到各种各样的问题。下面是我总结的一些常见场景和排查思路:
| 问题现象 | 可能原因 | 排查与尝试方向 |
|---|---|---|
| 包含路径后返回空白页 | 1. 文件不存在。 2. 文件存在但无打印输出。 3. 包含的文件中有语法错误,且错误显示被关闭。 |
1. 检查路径是否正确,尝试包含一个已知存在的文件(如 index.php )。 2. 使用 php://filter 读取源码,看文件内容是否为空或有逻辑问题。 3. 开启靶场的错误显示,或查看服务器错误日志。 |
使用 php://filter 仍被添加后缀 |
服务端过滤逻辑可能将整个参数字符串拼接后缀,伪协议被破坏。 | 尝试使用 ?page=php://filter/convert.base64-encode/resource=flag.php%00 (空字节截断,仅对PHP<5.3.4有效)。或尝试其他伪协议如 zip:// 或 phar:// (需配合文件上传)。 |
| 包含日志文件无反应 | 1. 日志路径错误。 2. 日志文件权限不足(Web用户无法读取)。 3. 注入的代码被转义或过滤。 |
1. 通过LFI包含 /proc/self/cwd/ 等来推断当前工作目录,再推测日志路径。 2. 尝试包含 /etc/passwd 等确认基本LFI是否有效,以排除权限问题。 3. 查看日志文件内容(如果可读),确认代码是否被原样写入。注意 <?php ?> 标签可能被部分WAF过滤。 |
| 伪协议被禁用 | 服务器可能通过配置或WAF禁用了部分伪协议。 | 尝试其他伪协议,如 file:// 、 http:// (如果allow_url_include开启)、 data:// 。或者回归到传统的路径遍历,尝试读取 /proc/self/environ (环境变量)或 /proc/self/fd/ (文件描述符)等特殊文件。 |
个人心得 :在利用LFI时,思维一定要灵活。不要只盯着 ../../etc/passwd 。多观察页面的反馈,一个Warning信息、一个异常的报错都可能成为突破口。善用Burp Suite的Intruder模块进行路径或参数的模糊测试(Fuzzing),可以帮助你快速发现可读的目录和文件。记住,信息收集永远是渗透测试中最重要的一环,有时 flag 并不直接藏在 flag.php 里,而是需要通过包含其他配置文件(如 config.php 、 dbconn.php )找到数据库凭证,进而从数据库中提取出来。
最后,对于学习者而言,手动搭建漏洞环境、亲手构造Payload、观察每一步的输入输出,远比只看文章收获大。理解漏洞的原理和防御之道,不仅能让你在CTF中游刃有余,更能帮助你在开发中写出更健壮、更安全的代码。安全是一个攻防对抗、持续学习的过程,每一次实战解析都是为了筑起更牢固的防线。
更多推荐
所有评论(0)