跳到主要内容

命令注入与黑名单绕过

命令注入的本质是 用户输入被拼进一个由 Shell 解释的字符串。只要最终执行走的是 bash -c 一类的解释路径,换行、变量展开、编码变形都可能是绕过黑名单的入口。

仅限授权环境

命令注入验证会真实执行系统命令,只能在授权靶场或明确授权的测试中进行。


常见根因模式

语言/场景缺陷写法
PHPshell_exec("cat logs/" . $_POST['file'])
Shell 包装bash -c "cmd ${VAR}"VAR 来自外部输入
Pythonsubprocess.check_output("ping -c 1 " + host, shell=True)

共同点:shell=True 或等价的字符串拼接调用,输入中的元字符会被 Shell 解释成命令分隔符、管道或替换语法。


黑名单绕过手法

换行与空白字符

换行符 %0a:多数黑名单只过滤 ; & | 这几个显式分隔符,换行符同样是合法的命令分隔符

file=logs/app.log%0aid

URL 解码后实际执行 cat logs/app.logid 两条命令。

空格用 Tab 替代:空格被过滤时用 %09(Tab),Shell 会把 Tab 当参数分隔符:

%0a%09id%09>&2

base64 编码整条命令

payload 中特殊字符被逐个过滤时,把命令整体 base64 编码再解码执行,注入串里只保留管道和字母数字,引号、分号、空格全部消失:

echo aWQ= | base64 -d | bash

命令长度受限的环境里,这层编码同时能 缩短注入串:长命令放在别处,注入串只留一层解码调用。


分隔符稳定性差分

;&&|$() 在不同包装方式(PHP shell_execbash -c、Python shell=True)下成功率不同——有的包装层会剥离某类字符,有的上下文里 && 被当普通文本。逐一测试并记录响应差分

分隔符测试输入判断依据
;x;id回显是否包含 uid=
&&x&&id同上
|x|id管道后半段输出是否出现
$()$(id)替换结果是否拼进原命令输出
%0ax%0aid换行后的命令是否执行

先用 id 这类必然产生 uid= 特征输出的命令做探测,确认可用分隔符后再上真实 payload。


无回显技巧

借 stderr 回显

$(cmd>&2) 把命令输出写到标准错误。部分框架只吞掉 stdout,但 stderr 会进入错误页或日志,从而带出结果:

$(id>&2)

配合 base64 包裹框架,规避对特殊字符的检查:

$(echo aWQ=|base64 -d|bash >&2)

输出落盘再读

os.system() 这类调用没有回显通道时,把输出重定向到可读取的位置,再用文件读取功能取回:

RUN id > /tmp/out.txt 2>&1

之后通过任意文件读取/下载接口读 /tmp/out.txt


复核要点

检查点说明
是否真的走了 Shellshell=True / shell_exec / bash -c 才可注入,argv 数组不可
分隔符验证逐个测试 ; && | $() %0a,记录差分
字符过滤面空格、引号、分号分别被过滤时选对应替代方案
回显通道直接回显 → stderr → 落盘,按顺序尝试

先用 id 级短命令验证,确认执行路径后再上真实 payload,避免误伤目标环境。


防御建议

  1. 避免 Shell 拼接:能不调用 Shell 就不调用,直接用库函数完成同等功能。
  2. 必须调用外部命令时使用参数化(argv 数组)并保持 shell=False
  3. 对输入做白名单校验(如文件名只允许 [A-Za-z0-9._-]),黑名单不可作为唯一防线。
  4. 确需在 Shell 层转义时使用 printf '%q',不要手写转义规则。
  5. ;|$()、反引号、换行等元字符进日志的位置保持监控与告警。