最有效方式是双重禁用危险函数:在php.ini和php-fpm pool配置中同步设置disable_functions,覆盖exec、system、shell_exec等全部高危函数,并重启与重载服务;同时禁用phar://、allow_url_include等辅助rce的机制。

PHP 8.5.5 中防止命令注入漏洞,核心不是“怎么加固某段代码”,而是**必须禁用 exec、system 等函数,并杜绝用户输入拼接到 shell 命令中**。只要这些函数还可用,且参数未经白名单校验就进命令行,防护就是纸糊的。
为什么 php.ini 的 disable_functions 必须配两次
只在 php.ini 里写 disable_functions = exec,system,shell_exec 不够——PHP-FPM Pool 配置能覆盖它,而某些 CMS 或框架会用 ini_set() 尝试绕过。你得在两个地方同时堵死:
-
php.ini中取消注释并填全:disable_functions = exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec,pcntl_fork,eval,assert - 在
/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,pcntl_fork,eval,assert - 改完必须分别执行
systemctl restart php8.5-fpm(重启)和systemctl reload php8.5-fpm(重载 Pool),缺一不可
escapeshellarg 和 escapeshellcmd 别混用
这两个函数常被误当“万能解药”,但它们作用对象完全不同,混用反而破坏安全性:
-
escapeshellarg()只用于**单个参数**,比如文件名、用户名:$safe_file = escapeshellarg($_GET['file']); exec("cat " . $safe_file); -
escapeshellcmd()是对**整条命令字符串**做元字符转义,比如:$cmd = "./backup.sh " . $_POST['target']; exec(escapeshellcmd($cmd)); - 绝对不要写成
escapeshellcmd("ls " . escapeshellarg($_GET['dir']))—— 单引号会被双重转义,导致命令解析失败或意外逃逸 - 二者都无法防御
--open-files-in-pager=id; rm -rf /这类利用选项解析机制的绕过,所以白名单封装才是底线
真要调用系统命令?那就只许走白名单路径
业务确实需要执行命令(如压缩、转换、ping 检测),就不能靠过滤或转义兜底,必须收口到可控函数中:
- 定义允许的命令集合:
$allowed_commands = ['ping', 'curl', 'convert']; - 对参数做强约束:IPv4 地址只用
filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);文件路径必须realpath()后落在/var/www/allowed/下 - 用
proc_open()替代exec(),手动控制 stdin/stdout/stderr,避免 shell 解析介入 - Web 进程用户(如
www-data)禁止对/etc、/root、/home有读写权限,ls -ld /etc要返回drwxr-xr-x 91 root root这类严格属主
最易被忽略的一点:PHP 8.5.5 虽已默认禁用 create_function(),但 unserialize() 仍可能通过魔术方法触发命令执行。只要没彻底禁用 phar:// 流封装器(需设 phar.readonly = On)和关闭 allow_url_include,LFI + Phar 反序列化就能绕过所有函数禁用。这层链路,比 exec 更隐蔽,也更难审计。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











