<!-- title: command-injection -->
命令注入详解:原理、利用与防御
一、漏洞原理
命令注入(Command Injection)是指应用把用户输入拼进操作系统命令中执行,攻击者通过命令分隔符附加自己的指令,从而在服务器上执行任意系统命令的漏洞。在 Web 漏洞的"危害排行榜"上,它几乎稳坐头把交椅——从"访问网站"直接跳到"控制服务器操作系统",中间没有任何过渡。
它与 SQL 注入同根同源,只是注入的对象从 SQL 语句换成了 Shell 命令。典型场景:在线图片压缩调用 convert、网络工具页执行 ping、备份功能调用系统命令打包文件——只要"命令的一部分"来自用户输入,就有机会被注入。
攻击得以成立的关键在于 Shell 的语法特性:cmd1; cmd2 顺序执行、cmd1 && cmd2 前者成功才执行、| 管道、$(cmd) 与反引号命令替换、换行符同样分隔命令。攻击者只要在合法参数后追加这些"粘合剂",自己的命令就会搭车执行。即使应用过滤了部分字符,${IFS} 代替空格、编码变形等技巧也层出不穷,这也解释了为什么过滤式防御从来不可靠。
二、示例代码
以下是一个存在命令注入的"在线 Ping 工具"(虚构站点 example.com):
<?php
// ping.php —— 用户输入目标主机,服务器执行 ping
$host = $_GET['host'];
echo "<pre>" . shell_exec("ping -c 4 " . $host) . "</pre>";
?>正常输入 host=example.com 相安无事;攻击者输入 host=example.com; cat /etc/passwd,服务器实际执行的是 ping -c 4 example.com; cat /etc/passwd——两条命令一次跑完。修复后的安全写法核心是"数据与命令彻底分离":
<?php
// 安全版:参数化执行 + 严格白名单校验
$host = $_GET['host'] ?? '';
// 1. 白名单校验:只允许合法主机名字符
if (!preg_match('/^[a-zA-Z0-9.-]{1,253}$/', $host)) {
http_response_code(400);
exit('参数非法');
}
// 2. escapeshellarg 把参数包裹成"纯数据",任何分隔符都会被转义
$output = shell_exec('ping -c 4 ' . escapeshellarg($host));
echo "<pre>" . htmlspecialchars($output ?? '', ENT_QUOTES, 'UTF-8') . "</pre>";
?>Python 中对应 subprocess.run(['ping', '-c', '4', host]) 的列表传参形式(禁用 shell=True);Java 中避免 Runtime.exec(String) 的单字符串重载。
三、三种典型 Payload(教学演示)
1. 分号追加命令(最经典的形态)
host=example.com; whoami分号是 Shell 的命令分隔符,whoami 的输出会混进页面回显中。测试注入存在性时,安全人员常用 ; id 或 `id` 这类无害命令做验证,这同样是日志审计中应重点监控的特征。
2. 管道与命令替换(绕过"追加"过滤)
host=example.com | type C:\windows\win.ini (Windows 管道)
host=$(whoami) (命令替换)
host=`id` (反引号)即使分号被过滤,管道符和 $()/反引号同样能把数据位变成命令位。这条 Payload 的教学意义:逐个拉黑特殊字符永远补不完,黑名单式防御在命令注入面前形同虚设。
3. 盲注验证(无回显场景)
页面不回显命令输出时,用延时或带外方式确认执行:
host=example.com && sleep 10
host=example.com && ping -n 10 127.0.0.1 (Windows 延时) <!-- allow -->响应时间明显变长即证明命令被执行。实战中还会配合 DNS/HTTP 外带把结果送出,原理与盲 XXE 的带外渗出一脉相承。
四、防御建议
- 根本原则:不调用系统命令。90% 的"必须执行命令"的场景都存在库替代——压缩用压缩库、图片处理用 GD/ImageMagick 绑定、DNS 查询用解析库,把 Shell 从链路中移除
- 参数化执行:无法避免时,使用数组式 API(Python
subprocess列表参数、NodeexecFile),永远禁用shell=True与字符串拼接式 exec - 白名单校验输入:对确定格式的参数(IP、域名、文件 ID)做强类型/正则白名单校验,任何特殊字符直接拒绝
- 转义兜底:拼接不可避免时用
escapeshellarg(PHP)等专用函数包住参数,绝不手写引号 - 权限最小化:Web 进程使用低权限专用账户运行,配合
disable_functions、容器隔离、只读文件系统,即使被注入也升不了权、动不了核心文件 - 监控与审计:对 Web 进程发起的异常子进程(如
bash、nc、curl向外连接)建立告警,命令注入成功后必有子进程与外联行为,这是检测的最后防线
五、总结
命令注入的防御铁律只有一句:永远不要让用户输入有机会成为 Shell 语法的一部分。能做到"不调命令"就用库,退一步用参数化执行,再退一步才谈转义——顺序不能反。对学习者而言,它是理解"注入类漏洞家族"的通用模板:SQL 注入、命令注入、模板注入、表达式注入,全是"数据越界成了代码"的变体,修法也同源——结构化 API + 白名单 + 最小权限。