disable_functions 必须用无空格英文逗号分隔小写函数名(如exec,system,passthru),否则解析失败致禁用失效;应仅禁高危函数如exec、shell_exec、proc_open、pcntl_exec等,避免误禁assert、unserialize等影响业务且风险低的函数。
disable_functions 为什么不能只填函数名列表
直接在 php.ini 里写 disable_functions = exec,system,passthru 看似干净,但漏掉关键细节:php 会把整个字符串当作单个函数名去匹配,中间的空格、换行、多余逗号都会导致解析失败,结果是——一个函数都没禁掉。
常见错误现象:phpinfo() 里看到 disable_functions 值为空,或只生效了第一个函数;线上还能调用 shell_exec 却以为已禁用。
- 必须用英文逗号分隔,且**逗号后不能有空格**(
exec,system,passthru✅,exec, system, passthru❌) - 值中不能换行,否则 PHP 截断解析,后续内容被忽略
- 大小写敏感:
EXEC不会被识别,必须小写 - 禁用列表不支持通配符或正则,只能逐个写死函数名
哪些函数真正该禁、哪些其实没必要动
禁太多会破坏依赖这些函数的合法组件(比如 Composer、Laravel 的文件锁、WordPress 的更新机制),禁太少又留后门。核心原则:只禁能直接执行系统命令或读写任意文件的函数,其余交给 open_basedir 和权限控制。
真正高危且应优先禁用的(常见于 WebShell 利用链):
-
exec、shell_exec、system、passthru(命令执行) -
popen、proc_open(进程控制,比 exec 更隐蔽) -
pcntl_exec(常被忽略,但可绕过部分 disable_functions 检查) -
curl_exec、file_get_contents(配合php://filter或 SSRF 可读敏感文件)
没必要禁的(影响大、风险低):
立即学习“PHP免费学习笔记(深入)”;
-
assert(PHP 7.2+ 已默认禁用动态代码执行,且新版更倾向用eval替代) -
create_function(PHP 8.0 已废弃,禁了也无实际意义) -
unserialize(本身不危险,危险的是反序列化对象,禁了反而让业务崩溃)
禁用后还要防绕过:proc_open 和 dl 的坑
很多人禁了 exec 就以为安全了,但攻击者可能用 proc_open 启动新进程,或用 dl(PHP
使用场景:WebShell 如 China Chopper、AntSword 默认优先尝试 proc_open,其次才是 exec;某些老版本 PHP 还支持 dl 动态加载 so/dll。
-
proc_open必须加入disable_functions,它比exec更难检测,且返回资源句柄,日志里不显眼 -
dl在 PHP 8.0+ 已移除,但若你还在用 7.x,务必禁用;检查php -m是否含dl模块 - 注意
putenv和ini_set:它们不能直接执行命令,但可配合其他漏洞修改环境变量或配置(如绕过 open_basedir),建议一并禁用
验证是否真生效:别只信 phpinfo()
phpinfo() 显示的 disable_functions 值只是 ini 配置项,不代表运行时真实生效——如果用了 php_admin_value 或容器覆盖,phpinfo() 可能不反映最终值。
最可靠的方式是直接测试调用:
echo @exec('id') ?: 'exec disabled';
但要注意:有些函数禁用后会触发 E_WARNING,而有些(如 proc_open)禁用后返回 false 但不报错,容易误判。
- 写个最小脚本,对每个要禁的函数都
var_dump(function_exists('xxx'))和try { xxx(); } catch {}双重验证 - 在 CLI 和 FPM 两种 SAPI 下分别测试,FPM 可能受 pool 配置覆盖(
php_admin_value[disable_functions]) - 重启服务后,用
ps aux | grep php确认新配置已加载,避免改了文件却没重启进程
最麻烦的点在于:不同 PHP 版本对同一函数的禁用行为不一致(比如 pcntl_exec 在某些 7.4 补丁版里仍可绕过),所以别只抄网上列表,得按你实际跑的版本实测。


















