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 条:
- 不拼接用户输入
- 使用 escapeshellarg
- 使用白名单验证
- 尽量不用 shell
如果你愿意,我可以给你:
- 🔥 一份 PHP 安全加固完整 checklist
- 🔥 一份 常见 Web 漏洞防御大全
- 🔥 WordPress 安全强化指南
- 🔥 企业服务器安全配置方案
你现在是遇到实际漏洞,还是做安全加固?
发表回复