正则表达式中有一种递归的用法,今天才知道。但是这种递归用法并不是所有语言都支持。它适用于PHP和java等语言。
以下列出几个简单的递归
(?R)?
(?0)?
\g<0>?
加号 , +
星号 *
问号 ? 一般来说问号也是递归的一种
一般用到最多就是?R。
括号里不能有参数,而有些题中就就会用这种递归的正则来过滤恶意函数,所以我们就用无参的函数像上图中套娃一样注入命令。
一般情况下的命令执行都是由参数的,比如system函数,eval函数,passthru函数等等。但是用无参函数套娃也是常见的绕过姿势。(今天算是见识到了)
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
}
在最近的春秋杯中web签到题就用到的这个代码来过滤有参函数。这段代码就是code传参的内容通过正则表达式匹配到的内容来替换为空,然后只能剩下;来满足强等。满足这种if条件后,传入的代码就会被eval函数来执行。这种正则就只能用无参函数来绕过限制。就比如a(b(c()))这种形式就满足,而a(b('c'))这种带参数的就不能执行成功。
正则的具体解析
这个函数的内容就是获取http所有的头部信息。接着我们可以用var_dump函数来把函数的执行结果都打印出来。这个函数有一个缺陷,它只能在apache中间件环境下使用。我们来在本地测试一下无参函数的具体效果。
测试代码:
我们将var_dump(getallheaders());语句传入进去。
可以看到head头部信息以一维数组的形式 打印出来。当然,这么多内容我们并不是都需要,我们需要选择指定我们想要的一条来执行得出我们想要的结果。
在php中end()函数是取出数组的最后一位。这个end函数是只会取出最后一位的键值,就是以字符串的形式输出出来,所以键名是可以随便起的。
我本地用var_dump输出getallheaders()函数的内容是倒着输的,(具体原因不清楚)所以使用end函数就截取了第一行的内容localhost。 其实原理是一样的。我们可以在http报文后加上我们要执行的恶意代码,再使用end函数截取最后一行,把var_dump函数换成eval函数就可以达到无参函数执行命令了。
这种方法就是把恶意代码写在cookie的session_id里面。然后就用session_id()这个函数来读取它,然后它会返回出一个字符串,再用eval函数执行命令。但是,这种方法的前提是我们要开启session才能用session_id(),也就是说,要先session_start();用var_dump函数输出一下
session_id(session_start()),出来的是字符串,我们不需要做任何转换,但是session字符串只能有大小写字母和字符串。但不包含字符,所以我们在session这个地方注入命令就要做一些转换了,那么我们可以将字符串进行16进制编码后再用php中的hex2bin函数解码,执行恶意命令。
可以进行eval函数执行注入在PHPSESSID中的命令。我们修改PHPSESSID的值,写入十六进制编码后的恶意代码。
我本地环境搞不了,所以借用了师傅的图。大佬博客:RCE篇之无参数rce - 学安全的小白 - 博客园
直接用php函数来读取我们想要的文件。
localeconv() 函数返回一包含本地数字及货币格式信息的数组。
乍一看这个函数跟文件读取也没关系啊,但就是这个占不到边的函数也能玩出新花样。它返回的是一个数组,数组的第一行是一个点(.) 这个点就可以代表当前目录了,我们可以用current()函数取出这个点。取出这个点之后,再用scandir函数进行目录遍历。
具体传参:var_dump(scandir(current(localeconv())));就可以遍历当前目录所有文件。
它还有别名pos(),其实都是一样的。
输出数组当前元素的值,每一个数组中都有一个内部的指针,指向他当前的元素。初始指向当前数组中的第一个元素。这个函数不会移动指针,但是有next()函数和prev函数可以移动指针。
列出当前目录的文件和目录,以数组的形式展现出来。
chdir() 函数改变当前的目录。这个函数是是用来跳目录的,有时我们要读取的文件不在当前目录下,所以我们要改变当前目录。比如用chdir('..')来跳回上一级目录。再配合scandir函数遍历任意目录下的文件。
根据单词意思就知道这个函数的功能就是将数组倒过来。
这个函数用来高亮代码,可以相当于文件读取。相当于show_source()。
这个函数返回当前工作目录。成功则返回当前工作目录,失败则返回FALSE。也需要用scandir函数遍历当前工作目录。
这个函数返回路径中的目录名称部分。
返回由所有已定义变量所组成的数组。
返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
具体使用参考菜鸟教程
这次用GXYCTF禁止套娃这个题目来切身感受一下。
这个涉及到了git泄露,用GitHack扫一下下载源码。
";
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__);
?>
可以看到源代码中出现了我们的核心代码
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
这里就只能用无参函数了。
首先我们需要一个打印函数print_r把内容输出出来,我们可以用localeconv()函数,它返回的是一个数组,那我们用current()函数来返回数组的第一个值,也就是. 表示当前目录。接着我们可以用
scandir()函数来遍历目录。
payload:
?exp=print_r(scandir(current(localeconv())));
就能找到在当前目录下有flag.php。
读取文件时我们可以用show_source来高亮源代码。上图可知flag.php在第三行,因为数组指针默认是指向第一行,所以我们可以用next()函数和array_reverse()函数让数组指针指向flag.php,,最后高亮源代码。
payload:
?exp=show_source(next(array_reverse(scandir(current(localeconv())))));
最后得出flag.。
当然,这并不是唯一的方法。我们还可以自己构造PHPSESSID来通过PHPSESSID来注入命令。
使用session_id(session_start())获取我们构造的恶意PHPSESSID。接着我们可以用print_r或
show_source函数来打印或者高亮源代码。具体操作不再演示。
php中还有很多像这样的无参函数,多做题,多积累。
RCE篇之无参数rce - 学安全的小白 - 博客园
[GXYCTF2019]禁止套娃 1 &无参数RCE - op_hxc - 博客园