命令注入是什么
命令注入的成因是把用户输入拼接进 shell 命令字符串:分号、管道、与号、反引号、命令替换等元字符让输入变成新的命令,服务器最终执行的不再是开发者预期的程序。它的危害是主机级的——一旦成功,攻击者获得的是运行服务的账号权限,可读取文件、发起内网请求、写入持久化配置。防护的第一选择是彻底绕开 shell,用参数数组方式调用程序;无法绕开时,除了严格的输入域限制,还要自行完成正确的引用与转义。
基本信息
| 风险类别 | OWASP Top 10 2021 A03 注入类;CWE-78 |
|---|---|
| 常见入口 | 网络诊断(ping、traceroute)、文件格式转换、备份脚本、系统信息查询 |
| 危险元字符 | 分号、管道、与号、反引号、命令替换、换行 |
| 危害 | 以服务账号权限执行任意命令,读取数据与横向探测 |
| 首选防护 | 参数数组调用,不经过 shell |
| 兜底手段 | 输入域白名单、最小权限运行、出站限制 |
典型场景
- 运维平台提供网络诊断按钮,把用户填写的地址拼进 ping 或 traceroute
- 媒体处理功能调用转换工具,把文件名拼进命令行
- 定时备份脚本读取数据库里的参数并拼进系统命令
- 日志分析功能调用外部文本工具处理用户上传的文件
- 系统信息查询页面调用平台命令并拼接参数
- 容器内应用调用宿主命令,权限与影响面更大
风险与安全对比
风险与安全对比
风险:os.system('ping -c 1 ' + ip)
安全:subprocess.run(['ping', '-c', '1', ip], check=True)
参数数组方式不经过 shell,元字符失去特殊含义
# 其他语言同理:Java 用 ProcessBuilder 的列表构造,PHP 用数组形式的 proc_open
# 避免使用拼接字符串后再交给 shell 的调用方式输入校验补充(Python)
输入校验补充(Python)
import ipaddress
try:
ip = str(ipaddress.ip_address(user_input))
except ValueError:
raise ValueError('invalid ip')
# 允许的输入域越窄(IP、域名、枚举值),风险面越小
# 域名场景要同时限定字符集与长度,并禁止以短横线开头结尾调用方式风险对照
| 调用方式 | 是否经过 shell | 风险 |
|---|---|---|
| 字符串拼接后交给 shell | 是 | 最高,元字符全部生效 |
| 参数数组调用 | 否 | 低,元字符按普通字符处理 |
| 必须使用 shell 的固定命令 | 是 | 中,参数需严格白名单与引用 |
| 调用内部函数库替代外部命令 | 否 | 最低,推荐优先采用 |
必须使用 shell=True 时,需要自行完成整体引用转义,出错概率高,应尽量避免。
运行权限也是防线:以专用低权限账号运行外部命令、限制其可写目录与出站网络,即使注入成功也能压缩影响范围。