disable_functions 必须写在主 php.ini 中,.user.ini 和 ini_set() 无效;需分别配置 Web(如 php-fpm)和 CLI 的 php.ini;exec/system/shell_exec/passthru/proc_open/popen/pcntl_exec/eval/assert/create_function/dl/call_user_func/call_user_func_array 应成组禁用;assert 还需单独关闭 assert.active = Off;禁用后须 Web 和 CLI 双环境验证。

disable_functions 配置必须写在主 php.ini,不是 .user.ini
PHP 8.0 生产环境禁用命令执行函数,disable_functions 只在主配置文件(即 php.ini)中生效,.user.ini 或 ini_set() 完全无效。IIS、Nginx + PHP-FPM、Apache 都只读取一次该指令,且不支持运行时覆盖。
常见错误是把 disable_functions = exec,system 写进网站根目录的 .user.ini,结果 php -r "echo exec('id');" 依然能跑通——因为 FastCGI 进程根本没加载它。
- 确认路径:访问
phpinfo()页面,查Loaded Configuration File行,确保你编辑的是那一份 - Windows IIS 用户注意:
C:\php\php.ini和C:\Windows\php.ini常被混淆,后者通常被忽略 - Linux 用户注意:
/etc/php/8.0/fpm/php.ini和/etc/php/8.0/cli/php.ini是两份独立配置,Web 和 CLI 环境需分别设置
exec system shell_exec passthru 必须成组禁用,单禁一个没用
攻击者只要留一个出口就能绕过。比如只禁 exec 但放行 shell_exec,system 和 passthru 同样可直接执行任意命令并回显。更危险的是 proc_open 和 popen,它们能建立双向管道,绕过大多数日志监控。
标准最小集应包含:
立即学习“PHP免费学习笔记(深入)”;
-
exec,system,shell_exec,passthru -
proc_open,popen,pcntl_exec -
eval,assert,create_function,dl -
call_user_func,call_user_func_array(防回调注入)
注意:preg_replace 的 /e 修饰符在 PHP 7.3+ 已移除,无需写进 disable_functions;但若旧代码残留该用法,会直接报错而非执行,属于“安全失败”,不必额外处理。
修复 Core bailout 状态损坏安全问题,以及 BZ2、DOM、PDO、Opcache/JIT 内存与执行错误,改进 Unicode 字素处理、URL 解析、TLS 流状态、信号分发及会话错误处理。
禁用后还要验证 CLI 和 Web 两个环境是否都生效
很多人只测了网页端,忘了命令行。ThinkPHP 的 php think、Laravel 的 php artisan 都走 CLI SAPI,如果 CLI 的 php.ini 没同步改,php -r "echo system('whoami');" 仍会成功输出。
验证步骤必须分两路走:
- Web 端:新建
test.php,内容为<?php echo system('id'); ?>,访问返回空白页且无输出(不是 500 错误) - CLI 端:在服务器终端执行
php -r "echo system('id');",应看到警告:Warning: system() has been disabled for security reasons - 额外检查:运行
php --ini确认 CLI 加载的配置路径,再用php -i | grep disable_functions直接看值
不要依赖 disable_functions 拦住 eval 或 assert 字符串参数
eval 和 assert 在 PHP 8.0+ 中仍支持字符串参数调用(如 assert('$x > 0')),且无法通过 disable_functions 彻底封死——它们是语言结构,不是普通函数。真正有效的做法是:关闭 allow_url_include = Off、设 display_errors = Off、并确保所有用户输入不进 eval 流程。
尤其 ThinkPHP 8.0 项目里,assert 常被用于调试条件断言,一旦上线未清理,配合可控变量就能 RCE。所以:
- 生产环境必须设
assert.active = Off(这是独立开关,和disable_functions无关) - 代码扫描要覆盖所有
eval(、assert(、create_function(出现位置 - 别信“我只禁了 exec,其他函数业务要用”——命令执行入口从来不止一个函数名
最易被忽略的是:即使所有函数都禁了,mail()、error_log() 配合特定配置仍可能触发命令执行(如 sendmail 路径可控),所以 disable_functions 只是第一道门,不是终点。


















