参考
无参数RCE,其实就是通过没有参数的函数达到命令执行的目的。
没有参数的函数什么意思?一般该类题目代码如下(或类似):
<?php
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])){
eval($_GET['exp']);
}
?>
具体可见buuctf [GXYCTF2019]禁止套娃 一题wp
常见绕过姿势
getallheaders()
getallheaders()
返回所有的HTTP头信息,但是要注意的一点是这个函数返回的是一个数组,而eval()
要求的参数是一个字符串,所以这里不能直接用,这时我们就要想办法将数组转换为字符串。正好implode()
这个函数就能胜任。
implode()
能够直接将getallheaders()
返回的数组转化为字符串,他的转化是直接拼接数组中字符串且是从最后开始输出(由于php版本不同,输出顺序也可能不同)
那么我们就可以在最后随意添加一个头,插入我们的恶意代码并将后面的内容注释掉
get_defined_vars()
该函数的作用是获取所有的已定义变量,返回值也是数组。不过这个函数返回的是一个二维数组,所以不能与implode
结合起来用。
使用current()
函数可以返回数组中的单元且初始指针指向数组的第一个单元。因为GET方式传入的参数存在该二维数组中的第一个一维数组,所以我们可以通过这个函数将其取出来get=a&shell=phpinfo();
后面传入的shell=phpinfo();
出现在了第一个数组的最后
end()
将 array 的内部指针移动到最后一个单元并返回其值
回忆一下之前的payload:?exp=eval(implode(getallheaders()));
,设想下:current()
是取出二维数组中的第一个(指针指向的那个)一维数组,用end()
就可以取出这个一维数组中的最后那个值,加上之前的payload你能想到什么?
新payload:?exp=eval(end(current(get_defined_vars())));&shell=phpinfo();
用这个payload的话就可以执行shell的命令了
session_id()
session_id()
可以用来获取/设置当前会话 ID。
那么可以用这个函数来获取cookie中的phpsessionid
了,并且这个值我们是可控的。
但其有限制:
文件会话管理器仅允许会话 ID 中使用以下字符:a-z A-Z 0-9 ,(逗号)和 - (减号)
解决方法:将参数转化为16进制传进去,之后再用hex2bin()
函数转换回来就可以了。
所以,payload可以为:?exp=eval(hex2bin(session_id()));
但session_id必须要开启session才可以使用,所以我们要先使用session_start。
最后,payload:?exp=eval(hex2bin(session_id(session_start())));
配合使用的函数
getchwd() 函数返回当前工作目录。
scandir() 函数返回指定目录中的文件和目录的数组。
dirname() 函数返回路径中的目录部分。
chdir() 函数改变当前的目录。
readfile() 输出一个文件。
current() 返回数组中的当前单元, 默认取第一个值。
pos() current() 的别名。
next() 函数将内部指针指向数组中的下一个元素,并输出。
end() 将内部指针指向数组中的最后一个元素,并输出。
array_rand() 函数返回数组中的随机键名,或者如果您规定函数返回不只一个键名,则返回包含随机键名的数组。
array_flip() array_flip() 函数用于反转/交换数组中所有的键名以及它们关联的键值。
array_slice() 函数在数组中根据条件取出一段值,并返回。
array_reverse() 函数返回翻转顺序的数组。
chr() 函数从指定的 ASCII 值返回字符。
hex2bin() — 转换十六进制字符串为二进制字符串。
getenv() 获取一个环境变量的值(在7.1之后可以不给予参数)。
localeconv() 函数返回一包含本地数字及货币格式信息的数组。