仅关闭allow_url_include不足以防御RFI,因php://、data://、日志文件等仍可被利用;需同步禁用allow_url_fopen、配置open_basedir并实施代码层白名单校验。

allow_url_include 关闭是防远程文件包含(RFI)最直接有效的手段,但光关这个远远不够——它只是堵住一条路,而攻击者手里还有 php://、data://、日志文件、临时文件等其他入口。
为什么只关 allow_url_include 不够用
很多开发者看到报错 “The PHP function allow_url_include is not enabled” 就以为关掉就安全了,其实不是。即使 allow_url_include = Off,以下情况仍可触发危险行为:
-
include($_GET['file'])仍可能被用于本地文件包含(LFI),比如读取/etc/passwd或/var/log/apache2/access.log -
php://filter协议不受allow_url_include控制,include 'php://filter/read=convert.base64-encode/resource=/etc/passwd'照样能执行 -
data://和expect://(若启用)也能绕过,尤其在旧版 PHP 或未禁用enable_dl的环境中 - 如果代码里还用了
file_get_contents()或fopen()拼接用户输入,allow_url_fopen = On就等于开了另一扇门
怎么真正关掉 allow_url_include
别只改配置文件就完事,得确认它真生效了:
- 编辑
php.ini,找到或添加这一行:allow_url_include = Off;没有这行时,PHP 默认就是Off,但显式写出来更可靠 - 顺手把
allow_url_fopen = Off也设上,虽然它不直接影响include(),但能拦住大量基于file_get_contents()的 RFI 利用链 - 改完必须重启服务:Apache 要
sudo systemctl restart apache2,PHP-FPM 要sudo systemctl restart php*-fpm(版本号按实际填) - 用
phpinfo()页面或命令行php -i | grep allow_url验证,输出必须是allow_url_include => Off,而不是 “no value” 或空 - 共享主机用户没权限改
php.ini?可用.htaccess(Apache)或user.ini(PHP ≥5.3)覆盖:php_flag allow_url_include off
open_basedir 不是可选项,是必配项
关掉远程包含后,攻击者会立刻转向目录遍历和敏感文件读取。这时候 open_basedir 就是第二道硬闸:
- 在
php.ini中设为类似open_basedir = /var/www/html/:/tmp/,注意路径末尾的/—— 缺了它会被当成前缀匹配,可能意外放行/var/www/html_bak/config.php - 虚拟主机环境可用
.htaccess:php_admin_value open_basedir "/var/www/site1/:/tmp/"(仅 Apache,Nginx 不支持) - 验证是否生效:写个测试脚本调用
ini_get('open_basedir'),返回值应是你设定的路径字符串,而不是空或false - 别把
/tmp/加进去就不管了——如果应用允许文件上传且没重命名,攻击者可能上传恶意脚本到/tmp/再通过include执行
代码层白名单才是最后一道防线
所有依赖用户输入决定包含路径的逻辑,都该被重构。动态拼接 include 是高危操作,无论配置多严:
- 把合法页面名硬编码进数组:
$valid_pages = ['home', 'about', 'contact']; - 接收参数后严格校验:
if (!in_array($_GET['page'], $valid_pages)) { http_response_code(403); exit; } - 包含时用固定前缀:
include 'pages/' . $_GET['page'] . '.php';—— 注意这里已确保$_GET['page']是白名单内值,不会出现../或协议伪类 - 存放模板的
pages/目录应在 Web 根目录外,或通过.htaccess禁止直接 HTTP 访问:Deny from all
include() 的参数彻底脱离用户控制——哪怕只有一处漏网,整套防护就形同虚设。

















