system()、exec() 报错是因为被 disable_functions 禁用,而非 php 8.3 特性;常见于生产环境配置,如 disable_functions = exec,passthru,shell_exec,system,proc_open,popen,调用时触发“has been disabled”警告。

system()、exec() 等函数调用时为什么直接报错?
PHP 8.3 本身不会让 system() 或 exec() 报错——报错只发生在你主动禁用了它们。生产环境常见配置是把它们写进 disable_functions,比如:
disable_functions = exec,passthru,shell_exec,system,proc_open,popen
这个设置生效后,任何脚本里调用 system("ls") 都会触发 Warning: system() has been disabled for security reasons。它不依赖 PHP 版本差异,只要 ini 生效就拦死。
容易踩的坑:
-
disable_functions是 PHP 解析层拦截,但绕过方式很多:比如用mail()的第五个参数触发命令执行(老漏洞)、或通过imap_open()、curl_setopt()等间接调用外部程序 - 某些容器镜像或云平台(如阿里云函数计算)默认已禁用,但没文档说明,你本地测试正常,上线就崩
- CI/CD 流水线里用
php -l检查语法,不会触发disable_functions检查,误以为代码“安全”
preg_replace() /e 修饰符虽已废弃,但还有人用 eval() 拼接?
/e 早在 PHP 7.0 就被移除,现在搜不到也不代表安全。真正危险的是手写 eval() 或 assert() 去执行用户输入,比如:
$input = $_GET['callback'];<br>assert("is_callable('{$input}')");
攻击者传 ?callback=phpinfo// 就能执行任意函数。PHP 8.3 对 assert() 行为做了收紧:当第二个参数是字符串时,仅在 zend.assertions = 1 且 assert.exception = 0 下才解释执行——但这不是默认值,很多人没改,依然危险。
关键点:
-
assert()不在disable_functions黑名单里,常被漏掉 -
eval()无法被disable_functions禁用(它是语言结构,不是函数),只能靠代码审计发现 - 框架里自定义模板引擎若支持表达式渲染(如 Twig 的
{{ 1+1 }}),底层若用eval()实现,风险同级
反序列化 + 动态属性 = RCE 链更难察觉
PHP 8.3 的 #[\AllowDynamicProperties] 和反序列化组合,会让 POP 链更隐蔽。比如一个类本来没声明 $callback 属性,但在 PHP 8.2 下可被反序列化注入;到了 8.3,如果该类加了 #[\AllowDynamicProperties],攻击者仍能塞入恶意字段,再借 __destruct() 触发 call_user_func($this->callback)。
修复 Core bailout 状态损坏安全问题,以及 BZ2、DOM、PDO、Opcache/JIT 内存与执行错误,改进 Unicode 字素处理、URL 解析、TLS 流状态、信号分发及会话错误处理。
修复动作要同步做两件事:
- 所有反序列化入口必须加
allowed_classes白名单,例如:unserialize($data, ['allowed_classes' => ['SafeLogger']]) - 检查所有带
#[\AllowDynamicProperties]的类,确认其魔术方法(__wakeup、__destruct)不直接使用未过滤的动态属性 - 禁用
unserialize()后,别用json_decode()替代就完事——如果旧数据是序列化格式,强行转 JSON 可能丢数据或引发逻辑错乱
Web 服务器层拦截比 PHP 层更可靠
靠 disable_functions 或代码层过滤,挡不住绕过。真正兜底的是 Web 服务器规则:
Apache(放在 .htaccess 或虚拟主机配置中):
<filesmatch><br> Require all denied<br></filesmatch>
Nginx(必须放在 location ~ \.php$ { ... } 外层):
location ~* /(shell|exec|system|passthru)\.php$ {<br> deny all;<br>}
注意:这类规则对 URL 路径有效,但对 POST 数据里的命令字符串无效——所以它只是最后一道网,不能替代输入校验和函数禁用。
最易被忽略的一点:很多团队只扫 *.php 文件,却忘了检查 .phtml、.php5、.php7 这些扩展名,攻击者上传 shell.phtml 就能绕过所有基于 .php 的拦截规则。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










