exec() 返回空但无报错,大概率是 www-data 用户权限不足;需用 exec('whoami', $out) 确认用户,用 $code 检查退出码,优先改文件权限或用 proc_open() 指定用户,避免 sudo 提权。

exec() 返回空但无报错,大概率是权限卡在 www-data 用户上
PHP 脚本执行 exec() 时用的是 Web 服务进程的系统用户(如 Nginx 下是 www-data,Apache 下常为 apache 或 daemon),不是你 SSH 登录的 root 或普通用户。所以即使命令在终端能跑通,Web 环境下仍可能静默失败。
验证方法:在 PHP 中加一句 exec('whoami', $out); var_dump($out);,看输出是不是 www-data —— 如果是,那所有命令都受限于该用户权限。
- 不要直接给
www-datasudo 权限(极危险) - 优先改目标命令的文件权限,比如
sudo chmod u+s /bin/ping(仅限可信、无参数注入风险的命令) - 更稳妥的做法是用
proc_open()启动子进程,并显式指定运行用户(需配合sudo -u+ 白名单配置)
disable_functions 没拦住 exec(),但实际调用仍失败
disable_functions 是 php.ini 的硬开关,但它只禁函数调用入口,不干预底层权限。也就是说,即使 exec 没被禁用,只要 www-data 没权限执行对应二进制,照样返回空或 return_var = 1(非零退出码)。
检查方式:把 $return_var 第三参数传进去,再打印它:exec('ping -c 1 127.0.0.1', $out, $code); var_dump($code);。常见值:1 表示命令未找到或权限拒绝,2 常见于 ping 被 SELinux 或 capabilities 限制。
立即学习“PHP免费学习笔记(深入)”;
- CentOS/RHEL 上注意 SELinux 是否启用:
getenforce,临时关闭测试:setenforce 0 - 某些发行版对
ping使用cap_net_raw+ep能力而非 setuid,www-data默认无此能力,需sudo setcap cap_net_raw+ep /bin/ping -
which ping和ls -l $(which ping)必须一起看,确认路径和权限位
命令路径找不到,不是环境变量问题而是 PATH 被重置
Web 服务进程启动时通常会重置 PATH,导致 /usr/sbin、/sbin 不在搜索路径里。这就是为什么 ifconfig、iptables 这类命令在终端可用,但在 PHP 中 exec('ifconfig') 却报“command not found”。
别去改系统级 /etc/profile 或 Web 服务的环境配置——太重且易冲突。直接用绝对路径最稳:
- 先在服务器终端查真实路径:
which ifconfig→ 得到/sbin/ifconfig - PHP 中写成:
exec('/sbin/ifconfig -a', $out) - 如果要兼容多环境,可封装一个查找逻辑:
exec("command -v ifconfig 2>/dev/null", $path); $cmd = !empty($path) ? $path[0] : '/sbin/ifconfig';
eval() 被禁但 exec() 还在,不代表就安全了
eval() 是语言构造器,disable_functions 确实无效;但 exec() 是函数,只要没出现在 disable_functions 列表里,就能被调用——哪怕它最终因权限失败,攻击者仍可能通过错误码、超时、输出长度等侧信道探测系统状态。
真正有效的防护不是靠“让它失败”,而是“让它没机会被滥用”:
- 业务上真不需要命令执行?直接在 php.ini 加:
disable_functions=exec,passthru,shell_exec,system,proc_open,popen - 若必须保留(如部署脚本、日志清理),确保调用点有强校验:
in_array($cmd, ['rsync', 'tar'], true)+ 参数白名单过滤,绝不拼接用户输入 - PHP 7.1 没有
open_basedir对系统命令的限制能力,所以权限隔离只能靠 OS 层(用户/组/capabilities/SELinux)
最常被忽略的一点:很多团队花时间调 exec() 的路径和权限,却忘了检查 allow_url_fopen 和 allow_url_include 是否关严——这两个开关一开,include('http://...') 就能绕过所有命令执行函数的限制,直接加载远程恶意代码。



















