php7.4文件管理实战:文件包含漏洞的防范与代码规范【实战】
PHP7.4中文件包含漏洞主要源于本地文件包含,开发者直接拼接用户输入、白名单未绑定绝对路径、未拦截伪协议等。防御需白名单用绝对路径、realpath验证结果、启用open_basedir并显式拒绝php://和data://协议,四环节缺一不可。
PHP 7.4 里的文件包含漏洞,不是“会不会出问题”的讨论题,而是“漏洞到底藏在哪、怎么防得住”的现实问题。默认配置下 allow_url_include 确实关了,但本地文件包含(LFI)照样高发——尤其是开发者在 include 或 require 里直接拼接用户输入时,basename() 用错、realpath() 漏校验、白名单考虑不周,只要有一环失守,/etc/passwd 就能被读走。
为什么 include($_GET['page']) 在 PHP 7.4 里依然危险
PHP 7.4 并没有修补逻辑层面的缺陷,只是收紧了部分默认配置。攻击者现在根本不依赖远程包含,而是专攻本地路径穿越和伪协议利用。常见误区是以为加了 .php 后缀就万事大吉,结果 ../ 照样能绕过目录限制,%00 截断在低版本遗留环境里仍然有效(虽然 PHP 7.4 默认禁用了 magic_quotes_gpc,但某些老容器或自编译环境可能还残留)。更要命的是:日志、Session、临时上传文件这些“非预期文件”,在 Docker 或云环境中路径和权限已经变了,可很多开发者还按传统思路来测试。
include($_GET['page'] . '.php')→ 攻击者传?page=../../../../var/log/apache2/access.log%00,如果服务器 PHP 版本或配置不当,仍然可能截断并包含日志。- Docker 环境中
/var/log/apache2/access.log通常是/dev/stdout的软链,include()会报failed to open stream: Permission denied——但这不代表漏洞不存在,只是利用路径变了。 - PHP 7.4 对
php://filter依然完全支持:?page=php://filter/convert.base64-encode/resource=index.php直接源码泄露,不需要任何文件写入权限。
白名单必须绑定绝对路径,不能只校验文件名
只靠 in_array() 判断 $_GET['page'] 是否在数组里,等于给攻击者留后门——因为根本没约束文件的真实位置。PHP 7.4 的 realpath() 行为更严格了,但要是先拼路径再调用,照样可能被绕过。
- 错误写法:
include('pages/' . $_GET['page'] . '.php');,就算$_GET['page']值是about,攻击者也能构造about%00../config(取决于底层 libc 和 PHP 编译选项)。 - 正确做法:白名单用关联数组存完整绝对路径,比如
$whitelist = ['home' => '/var/www/html/pages/home.php', 'about' => '/var/www/html/pages/about.php'];。 - 获取参数后,不拼接、不路径操作,直接查键:
if (isset($whitelist[$page])) { include($whitelist[$page]); }。 - 额外加固:对白名单里的每个路径,用
realpath($path)预先计算一次并缓存,确保它确实落在/var/www/html/下——防止 symlink 绕过。
filter 伪协议和 data:// 协议必须显式拦截
PHP 7.4 默认允许 php://filter 和 data://,它们不依赖文件系统,而是内存级解析,即使关闭 allow_url_fopen 也没用。这是 LFI 转 RCE 的关键跳板,尤其在没法写 shell 的场景里特别致命。
php://filter用于读取并编码任意 PHP 文件:include('php://filter/convert.base64-encode/resource=config.php');→ 返回 base64 编码后的源码。data://可以直接执行代码:include('data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+');→ 解码后执行。- 防御措施不是靠黑名单(太容易遗漏),而是在包含前强制检查协议:
$path = $_GET['page']; if (preg_match('/^php://|data:///i', $path)) { die('Forbidden'); }。 - 更稳妥的办法:白名单机制天然排斥所有协议,因为白名单值全是本地磁盘路径,根本不包含
://。
PHP 7.4 的 realpath() 和 open_basedir 配合才真正生效
realpath() 在 PHP 7.4 中对符号链接和越界路径处理更严谨了,但它本身不阻止包含——只是返回规范路径。必须配合 open_basedir 才能从运行时层面锁死访问范围。
- 单独用
realpath($user_input)后判断是否以/var/www/html开头,仍然可能被/var/www/html/../etc/passwd绕过(如果realpath()返回的是/etc/passwd,而你忘了二次校验)。 - 正确流程:
$abs = realpath($user_input); if ($abs === false || strpos($abs, '/var/www/html') !== 0) { die(); } include($abs);。 - 终极保险:在
php.ini中设置open_basedir = /var/www/html:/tmp,这样即使代码漏判,PHP 运行时也会拒绝访问/etc下的任何文件。 - 注意:
open_basedir不影响php://和data://,所以协议拦截仍然不能省略。
PHP 7.4 的文件包含漏洞防御,核心不在于函数版本新旧,而在于路径控制是否形成了闭环——白名单必须带绝对路径、realpath 要验证结果、open_basedir 要启用、伪协议要显式拒绝。四个环节缺一不可,少一个,攻击者就能找到突破口。


































