最可靠低成本的防御是禁用 allow_url_include 并强制白名单路径;必须关闭 allow_url_include 和 allow_url_fopen;代码层禁止动态拼接,只用硬编码白名单数组查键;路径校验须用 realpath() 加目录前缀比对。

直接禁用 allow_url_include 并强制走白名单路径,是目前最可靠、成本最低的防御方式。其他任何“过滤 ../”“正则校验后缀”的方案,在真实攻击场景中都已被反复绕过。
php.ini 必须关闭 allow_url_include 和 allow_url_fopen
这不是可选项,而是底线配置。即使业务完全不主动用远程包含,攻击者仍可能通过 file_get_contents() + 伪协议(如 php://filter)或日志注入触发 RFI/LFI 链路。
-
allow_url_include = Off:必须设为Off,ini_set()在运行时无效,改完要重启 PHP-FPM 或 Apache -
allow_url_fopen = Off:虽不直触include,但会削弱file_get_contents、fopen等函数的攻击面 - 检查是否生效:
php -i | grep allow_url,确认输出为off或空值
代码层禁止动态拼接,只用白名单数组查键
所有形如 include($_GET['page'] . '.php') 或 require($dir . '/' . $input) 的写法,无论加了多少 str_replace 或 basename,都等同于开门揖盗。
- 白名单必须是硬编码的关联数组,例如:
$pages = ['dashboard' => 'dashboard.php', 'profile' => 'user/profile.php'] - 用户输入仅作键名使用:
$key = $_GET['page'] ?? 'dashboard'; if (!isset($pages[$key])) { die('400'); } - 拼接路径时用
__DIR__或dirname(__FILE__),避免依赖$_SERVER['DOCUMENT_ROOT'](可能被伪造) - 绝不信任扩展名:白名单里已含
.php,不再做$input . '.php'拼接
路径校验不能只靠 basename(),必须用 realpath() + 目录前缀比对
当业务真有动态路径需求(如 CMS 插件加载),basename() 只能防住最基础的 ../,对 ..%2f、%00、.///、双写绕过完全无效。
- 先清理输入:
$file = basename($_GET['file']); - 再拼基础路径:
$full_path = __DIR__ . '/modules/' . $file; - 必须调用
realpath($full_path)获取规范绝对路径 - 检查是否仍在预期目录内:
if (strpos(realpath($full_path), __DIR__ . '/modules/') !== 0) { die('Access denied'); } - 注意:
realpath()对不存在的文件返回false,需前置file_exists()判断
别碰 open_basedir 当主防线,它容易被绕过且干扰正常功能
open_basedir 是环境级限制,不是代码逻辑防护。它在容器、多租户、Composer 自动加载等场景下极易引发 failed to open stream 错误,且无法阻止同一受限目录内的恶意文件读取(如 session 文件、error_log)。
- 仅作为纵深防御补充,不能替代白名单和
allow_url_include = Off - 若启用,务必用
realpath(__DIR__)动态生成路径,避免硬编码导致子目录失效 - 现代项目(尤其含 Composer)基本不兼容
open_basedir,优先靠代码层收敛
真正难的是让团队所有人放弃“我这个 str_replace('../', '', $x) 已经很安全了”的侥幸心理——只要参数进到 include 或 require 的括号里,且没经过白名单键查找,就还没脱离风险区。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











