Skip to content

<!-- title: command-injection -->

命令注入详解:原理、利用与防御

一、漏洞原理

命令注入(Command Injection)是指应用把用户输入拼进操作系统命令中执行,攻击者通过命令分隔符附加自己的指令,从而在服务器上执行任意系统命令的漏洞。在 Web 漏洞的"危害排行榜"上,它几乎稳坐头把交椅——从"访问网站"直接跳到"控制服务器操作系统",中间没有任何过渡。

它与 SQL 注入同根同源,只是注入的对象从 SQL 语句换成了 Shell 命令。典型场景:在线图片压缩调用 convert、网络工具页执行 ping、备份功能调用系统命令打包文件——只要"命令的一部分"来自用户输入,就有机会被注入。

攻击得以成立的关键在于 Shell 的语法特性:cmd1; cmd2 顺序执行、cmd1 && cmd2 前者成功才执行、| 管道、$(cmd) 与反引号命令替换、换行符同样分隔命令。攻击者只要在合法参数后追加这些"粘合剂",自己的命令就会搭车执行。即使应用过滤了部分字符,${IFS} 代替空格、编码变形等技巧也层出不穷,这也解释了为什么过滤式防御从来不可靠

二、示例代码

以下是一个存在命令注入的"在线 Ping 工具"(虚构站点 example.com):

php
<?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
<?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. 分号追加命令(最经典的形态)

text
host=example.com; whoami

分号是 Shell 的命令分隔符,whoami 的输出会混进页面回显中。测试注入存在性时,安全人员常用 ; id`id` 这类无害命令做验证,这同样是日志审计中应重点监控的特征。

2. 管道与命令替换(绕过"追加"过滤)

text
host=example.com | type C:\windows\win.ini        (Windows 管道)
host=$(whoami)                                     (命令替换)
host=`id`                                          (反引号)

即使分号被过滤,管道符和 $()/反引号同样能把数据位变成命令位。这条 Payload 的教学意义:逐个拉黑特殊字符永远补不完,黑名单式防御在命令注入面前形同虚设

3. 盲注验证(无回显场景)

页面不回显命令输出时,用延时或带外方式确认执行:

text
host=example.com && sleep 10
host=example.com && ping -n 10 127.0.0.1        (Windows 延时) <!-- allow -->

响应时间明显变长即证明命令被执行。实战中还会配合 DNS/HTTP 外带把结果送出,原理与盲 XXE 的带外渗出一脉相承。

四、防御建议

  1. 根本原则:不调用系统命令。90% 的"必须执行命令"的场景都存在库替代——压缩用压缩库、图片处理用 GD/ImageMagick 绑定、DNS 查询用解析库,把 Shell 从链路中移除
  2. 参数化执行:无法避免时,使用数组式 API(Python subprocess 列表参数、Node execFile),永远禁用 shell=True 与字符串拼接式 exec
  3. 白名单校验输入:对确定格式的参数(IP、域名、文件 ID)做强类型/正则白名单校验,任何特殊字符直接拒绝
  4. 转义兜底:拼接不可避免时用 escapeshellarg(PHP)等专用函数包住参数,绝不手写引号
  5. 权限最小化:Web 进程使用低权限专用账户运行,配合 disable_functions、容器隔离、只读文件系统,即使被注入也升不了权、动不了核心文件
  6. 监控与审计:对 Web 进程发起的异常子进程(如 bashnccurl 向外连接)建立告警,命令注入成功后必有子进程与外联行为,这是检测的最后防线

五、总结

命令注入的防御铁律只有一句:永远不要让用户输入有机会成为 Shell 语法的一部分。能做到"不调命令"就用库,退一步用参数化执行,再退一步才谈转义——顺序不能反。对学习者而言,它是理解"注入类漏洞家族"的通用模板:SQL 注入、命令注入、模板注入、表达式注入,全是"数据越界成了代码"的变体,修法也同源——结构化 API + 白名单 + 最小权限。

仅用于学习交流的防守型安全知识库