disable_functions仅禁用PHP内置函数(如exec、system),对eval、assert、反序列化、扩展函数及语言结构无效;配置须小写、无空格、无换行,且需确认生效位置并重启服务。

禁用危险函数不能只靠 disable_functions 一行配置就以为万事大吉——它只拦得住内置函数调用,对 eval、assert、反序列化链、扩展函数(如 imap_open 触发的 popen)完全无效,且极易因配置位置错、大小写/空格错误、未重启而形同虚设。
怎么确认改的是正在运行的 php.ini
很多人改了 /etc/php/8.2/cli/php.ini,结果 Web 请求照样能 exec('id')。因为 Apache/FPM 用的是另一份配置:
- Web 环境下必须看
phpinfo()页面里 “Loaded Configuration File” 的路径,不是 “Configuration File (php.ini) Path” - FPM 用户尤其注意:
www.conf里的php_admin_value[disable_functions]会覆盖php.ini,优先级更高 - CLI 模式要单独验证:
php --ini查路径,php -r "echo ini_get('disable_functions');"看值 - Docker 或 IDE 内置服务器(如 PHP Built-in Server)可能用完全独立的配置,需单独处理
disable_functions 写法错一个字符就全失效
这个配置项解析极脆弱,常见“写了等于没写”的原因全是格式问题:
- 逗号后**绝对不能有空格**:
exec, system❌ → 只禁了exec,system被当成了字符串一部分丢弃 - 函数名**必须小写**:
EXEC、Shell_exec都不会被识别 - 行末不能有多余逗号:
exec,system,❌ → 整行解析失败,disable_functions值变为空 - 不能换行写,PHP 不支持跨行解析;注释符
;后内容会被截断 - 推荐写法:
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec,putenv,ini_set
为什么禁了 exec 还能执行命令
攻击者根本不用你禁掉的那几个“经典函数”。绕过手段真实存在且常被忽略:
立即学习“PHP免费学习笔记(深入)”;
-
proc_open是首选绕过项:China Chopper、AntSword 默认先试它,返回资源句柄,日志不显眼,权限同exec -
pcntl_exec在 FPM 下可绕过部分检测,尤其当 PHP 编译时带--enable-pcntl -
dl()(PHP 7.x)可动态加载恶意 so/dll,执行任意代码;PHP 8.0+ 已移除,但旧环境务必禁 -
putenv+mail()或其他函数组合,可在低版本中触发命令执行(CVE-2018-19537 类漏洞) -
create_function+preg_replace(PHP disable_functions 完全不拦截
禁用后必须实测,别信 phpinfo() 里的显示
phpinfo() 显示 disable_functions 有值,不代表真正生效。验证必须走执行流:
- 写测试脚本,直接调用:
<?php var_dump(exec('id'));—— 成功禁用会输出NULL并报PHP Warning: exec() has been disabled for security reasons - 检查返回值是否为
NULL,而不是靠有没有输出判断(display_errors=Off时页面可能空白) - 用
get_disabled_functions()获取当前运行时实际禁用列表,比读配置更可靠 - 重点测组合行为:
file_put_contents('/tmp/shell.php', '=`$_GET[x]`;'); echo file_get_contents('/tmp/shell.php');是否还能落地+触发 - 日志里搜
has been disabled,确认警告真实产生(有些环境把E_WARNING屏蔽了)
真正麻烦的从来不是“怎么写配置”,而是禁掉一个函数后,业务组件(Composer、Laravel 文件锁、WordPress 更新)突然报错,又或者漏掉 pcntl_fork 导致绕过检测。安全配置不是填完就结束,是每次上线前都要跑一遍函数探测 + 行为验证的闭环动作。



















