PHP 防止 Shell 命令注入(Command Injection)是生产安全的高危重点
一句话原则:

永远不要把用户输入直接拼接进 shell 命令。

下面给你一份 完整的企业级防护指南


一、什么是 Shell 命令注入?

危险写法示例:

$ip = $_GET['ip'];
system("ping " . $ip);

攻击者输入:

127.0.0.1; rm -rf /

最终执行:

ping 127.0.0.1; rm -rf /

直接被执行第二条命令。


二、PHP 中高风险函数

以下函数都会执行系统命令:

  • system()
  • exec()
  • shell_exec()
  • passthru()
  • popen()
  • proc_open()
  • 反引号 `command`

⚠️ 只要涉及用户输入,就必须处理。


三、最有效的防御方法


✅ 方法一:永远不要拼接字符串

❌ 错误写法:

system("ping " . $_GET['ip']);


✅ 方法二:使用 escapeshellarg()

这是最基础安全方法。

$ip = escapeshellarg($_GET['ip']);
system("ping " . $ip);

效果:

127.0.0.1; rm -rf /

会变成:

'127.0.0.1; rm -rf /'

不会被解析为多个命令。


✅ 方法三:使用 escapeshellcmd()

$cmd = escapeshellcmd("ping " . $_GET['ip']);
system($cmd);

⚠️ 注意:

  • escapeshellcmd() 适用于整个命令
  • escapeshellarg() 适用于参数
  • 更推荐 escapeshellarg()

✅ 方法四:白名单验证(最安全)

例如只允许 IP 地址:

$ip = $_GET['ip'];

if (!filter_var($ip, FILTER_VALIDATE_IP)) {
    die("Invalid IP");
}

system("ping " . escapeshellarg($ip));

或者限制固定命令:

$allowed = ['start', 'stop', 'restart'];

$action = $_GET['action'];

if (!in_array($action, $allowed)) {
    die("Invalid action");
}


✅ 方法五:使用 proc_open 并分离参数(高级推荐)

$ip = $_GET['ip'];

if (!filter_var($ip, FILTER_VALIDATE_IP)) {
    die("Invalid IP");
}

$process = proc_open(
    ['ping', $ip],
    [
        1 => ['pipe', 'w'],
        2 => ['pipe', 'w']
    ],
    $pipes
);

优点:

  • 不经过 shell
  • 不解析特殊字符
  • 参数分离执行

这是最安全方式


四、最佳实践架构建议


1️⃣ 禁用危险函数(生产必须)

php.ini:

disable_functions = system,exec,shell_exec,passthru,popen,proc_open


2️⃣ 使用 sudo 白名单机制

如果必须执行系统命令:

编辑 sudoers:

www-data ALL=(ALL) NOPASSWD: /usr/bin/safe_script.sh

然后:

exec("sudo /usr/bin/safe_script.sh");

只允许固定脚本。


3️⃣ 把逻辑写进固定脚本

不要让 PHP 直接执行动态命令。

错误做法:

exec("rm -rf /tmp/" . $_GET['file']);

正确做法:

exec("/usr/local/bin/delete_tmp.sh");

脚本内部自行验证。


五、常见攻击方式

攻击者可能输入:

; ls
&& whoami
| cat /etc/passwd
`id`
$(rm -rf /)

如果没过滤,全部会执行。


六、真实生产安全策略

企业级必须做到:

  • 输入白名单
  • 参数过滤
  • 使用 escapeshellarg
  • 不使用 shell_exec
  • 限制 PHP 运行权限
  • 不用 root 运行 PHP-FPM

七、对比安全等级

方法安全等级
拼接字符串❌ 极危险
escapeshellcmd⭐⭐
escapeshellarg⭐⭐⭐
白名单验证⭐⭐⭐⭐
proc_open 参数数组⭐⭐⭐⭐⭐

八、终极安全建议

最安全的方式:

$allowedIps = ['127.0.0.1', '8.8.8.8'];

$ip = $_GET['ip'];

if (!in_array($ip, $allowedIps)) {
    exit("Forbidden");
}

$process = proc_open(
    ['ping', $ip],
    [1 => ['pipe', 'w']],
    $pipes
);

不拼接字符串
不经过 shell
限制输入范围


九、如果你的项目是 WordPress

像你之前提到的 WordPress,如果有插件使用:

shell_exec($_GET['cmd']);

那是高危漏洞。

必须:

  • 立即删除
  • 升级插件
  • 做代码审计

十、总结

防止 Shell 注入的核心只有 4 条:

  1. 不拼接用户输入
  2. 使用 escapeshellarg
  3. 使用白名单验证
  4. 尽量不用 shell

如果你愿意,我可以给你:

  • 🔥 一份 PHP 安全加固完整 checklist
  • 🔥 一份 常见 Web 漏洞防御大全
  • 🔥 WordPress 安全强化指南
  • 🔥 企业服务器安全配置方案

你现在是遇到实际漏洞,还是做安全加固?