必须在php.ini和PHP-FPM pool中双重配置disable_functions,严格遵循全小写、无空格、无换行、无末尾逗号的语法,并禁用proc_open、curl_exec等易被绕过函数,再分别验证CLI与Web环境生效。

直接禁用 exec、system、proc_open 这类函数,只是防挂马的起点,不是终点。真正有效的防护必须覆盖调用链、配置生效路径和绕过手段三处关键。
disable_functions 写错一个字符就等于没禁
disable_functions 解析极脆弱,常见“看着写了,实际全失效”:
- 函数名必须全小写:EXEC 或 Shell_exec 不会被识别
- 逗号后不能有空格:exec, system ❌ 只禁了 exec,system 被当作文本丢弃
- 行末不能多逗号:exec,system, ❌ 整行解析失败,值变为空
- 不能换行写,PHP 不支持跨行读取;注释符 ; 后内容被截断
- 推荐写法:disable_functions = exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec,curl_exec,file_get_contents,eval,assert,symlink,link,chmod,chown,chgrp,putenv,ini_set必须在 PHP-FPM pool 中重复设置 php_admin_value[disable_functions]
php.ini 的设置可被应用层用 ini_set() 或扩展绕过;FPM pool 配置优先级更高,且不可被运行时修改:
- 编辑 /etc/php/8.5/fpm/pool.d/www.conf
- 在 [www] 段内添加:php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec,curl_exec,file_get_contents,eval,assert,symlink,link,chmod,chown,chgrp,putenv,ini_set
- 用 php_admin_value(不是 php_flag),否则字符串赋值会失败
- 执行 systemctl reload php8.5-fpm,不要只 restart,避免服务中断
禁了 exec 还能挂马?这些绕过点常被忽略
攻击者根本不会只试exec:
- proc_open 是 WebShell(如 AntSword)默认首选,返回资源句柄,日志不显眼,权限等同 exec
- curl_exec + SSRF 可打内网、读取 /etc/shadow 或配合 php://filter 包含恶意代码
- putenv + mail() 在旧版本中可触发命令执行(CVE-2018-19537 类漏洞)
- file_put_contents + include 组合,只要上传目录未被 open_basedir 严格隔离,就能写 Shell 并包含执行
- assert 在 PHP 8.5.7 中仍支持字符串参数执行(assert("system('id')")),必须禁
禁用后不实测 = 白禁
phpinfo() 显示有值 ≠ 真生效:
- 写测试脚本:<?php var_dump(exec('id')); ?>,成功禁用应输出 NULL 并报 Warning: exec() has been disabled
- CLI 和 FPM 必须分别验证:php -r "var_dump(exec('id'));" 和 Web 请求走一遍
- Docker、内置服务器、IDE 自带 PHP Server 均用独立配置,需单独检查 php --ini 和 php -r "echo ini_get('disable_functions');"真正卡住挂马入口的,不是函数列表有多长,而是每一条是否在所有执行上下文中都真实拦截 —— 尤其是 proc_open 和 curl_exec 这两个最常被漏掉的点。



















