PHP 7.4 文件包含漏洞仍高危,因逻辑缺陷未修复,LFI可通过路径穿越、伪协议(php://filter/data://)绕过防护;防御须闭环:白名单用绝对路径、realpath校验、open_basedir启用、显式拦截伪协议。

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免费学习笔记(深入)”;
-
php://filter用于读取并编码任意 PHP 文件:include('php://filter/convert.base64-encode/resource=config.php');→ 返回 base64 编码后的源码 -
data://可直接执行代码:include('data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+');→ 解码后执行<?php phpinfo();?> - 防御手段不是靠黑名单(易漏),而是在包含前强制检查协议:
$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 要启用、伪协议要显式拒绝。四个环节缺一不可,少一个,攻击者就能找到缝隙。



















