allow_url_include必须设为Off以防御RFI,且需配合白名单+realpath路径校验防LFI;php.ini或PHP-FPM配置修改后须重启服务,runtime设置无效。

allow_url_include 必须为 Off,否则 include、require 等函数仍可能加载远程 URL —— 这是 PHP 8.5.5 中唯一决定远程文件包含(RFI)是否可触发的开关,与 allow_url_fopen 无关。
检查并关闭 allow_url_include
PHP 8.5.5 默认已设为 Off,但旧配置迁移或手动开启后极易遗漏。运行时调用 ini_set('allow_url_include', '0') 无效,必须在启动阶段生效。
- 执行
php -i | grep allow_url_include或在脚本中输出ini_get('allow_url_include'),确认返回值是""或"Off" - 编辑
php.ini,确保存在且未被注释的行:allow_url_include = Off - 若使用 PHP-FPM,也可在 pool 配置(如
www.conf)中加:php_admin_flag[allow_url_include] = off - 修改后必须重启 PHP-FPM 和 Web 服务器(如
systemctl restart php-fpm nginx),否则不生效
include 参数不能拼接用户输入
即使 allow_url_include 关闭,include($_GET['page']) 仍存在本地文件包含(LFI)风险,比如通过 ../../../etc/passwd 泄露系统文件。
- 禁止任何形式的字符串拼接:
include($_GET['file'] . '.php')、require($dir . '/' . $_POST['name'])都不可接受 - 白名单必须硬编码,且仅作为键或值使用,例如:
$pages = ['home' => '/var/www/html/pages/home.php', 'about' => '/var/www/html/pages/about.php']; - 接收参数后只做键查找:
if (!isset($pages[$page])) { http_response_code(403); exit; },再include $pages[$page]; - 所有白名单路径应为绝对路径,避免依赖当前工作目录(
__DIR__可用,但需确认其指向安全位置)
路径校验不能只靠 basename()
basename() 是纯字符串操作,对 %00、..%2f、.//./ 或符号链接完全无感,单独使用等于没校验。
- 正确顺序:先
urldecode(),再basename(),再realpath(),最后比对根目录前缀 - 示例:
$root = realpath('/var/www/html/pages/');(末尾带斜杠!);$target = realpath('/var/www/html/pages/' . basename(urldecode($_GET['f']))); - 必须检查
$target === false(路径非法或不存在),再用strpos($target, $root . '/') === 0判定是否在授权范围内 - 不要用
str_starts_with()—— PHP 8.0+ 才支持,而 PHP 8.5.5 虽然支持,但生产环境可能混用更低版本
顺手关掉 allow_url_fopen 和危险协议
allow_url_fopen = Off 不影响 RFI 防御(RFI 只取决于 allow_url_include),但它能缩小攻击面,防止 file_get_contents('http://...') 类调用被滥用于 SSRF 或日志注入。
立即学习“PHP免费学习笔记(深入)”;
- 同时禁用伪协议:
php://filter、data://、phar://在 LFI 场景下危害极大,可通过disable_functions限制相关函数(如file_get_contents、readfile),或在open_basedir中排除敏感路径 -
open_basedir应明确限定为应用目录,例如:open_basedir = /var/www/html/:/tmp/(注意末尾斜杠) - 上传目录(如
/var/www/html/uploads/)不应出现在open_basedir允许列表中,或需单独设置更窄范围
allow_url_include 是单点防线,但一旦失效,整个包含逻辑就崩塌;白名单和路径校验是代码层兜底,二者缺一不可。最容易被忽略的是 realpath() 返回 false 的情况——它不代表“文件不存在”,而是“路径不可解析”,必须统一拦截,不能跳过。



















