无参数RCE

正则的递归

正则表达式中有一种递归的用法,今天才知道。但是这种递归用法并不是所有语言都支持。它适用于PHP和java等语言。

以下列出几个简单的递归

(?R)?
(?0)?
\g<0>?
加号 , +
星号 *
问号 ? 一般来说问号也是递归的一种

一般用到最多就是?R。

无参数RCE_第1张图片

括号里不能有参数,而有些题中就就会用这种递归的正则来过滤恶意函数,所以我们就用无参的函数像上图中套娃一样注入命令。

无参数RCE

一般情况下的命令执行都是由参数的,比如system函数,eval函数,passthru函数等等。但是用无参函数套娃也是常见的绕过姿势。(今天算是见识到了)

核心代码



if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
    eval($_GET['code']);
}

在最近的春秋杯中web签到题就用到的这个代码来过滤有参函数。这段代码就是code传参的内容通过正则表达式匹配到的内容来替换为空,然后只能剩下;来满足强等。满足这种if条件后,传入的代码就会被eval函数来执行。这种正则就只能用无参函数来绕过限制。就比如a(b(c()))这种形式就满足,而a(b('c'))这种带参数的就不能执行成功。

正则的具体解析

无参数RCE_第2张图片

无参函数

getallheaders()

这个函数的内容就是获取http所有的头部信息。接着我们可以用var_dump函数来把函数的执行结果都打印出来。这个函数有一个缺陷,它只能在apache中间件环境下使用。我们来在本地测试一下无参函数的具体效果。

测试代码:

我们将var_dump(getallheaders());语句传入进去。

无参数RCE_第3张图片

可以看到head头部信息以一维数组的形式 打印出来。当然,这么多内容我们并不是都需要,我们需要选择指定我们想要的一条来执行得出我们想要的结果。

end()

在php中end()函数是取出数组的最后一位。这个end函数是只会取出最后一位的键值,就是以字符串的形式输出出来,所以键名是可以随便起的。

无参数RCE_第4张图片

我本地用var_dump输出getallheaders()函数的内容是倒着输的,(具体原因不清楚)所以使用end函数就截取了第一行的内容localhost。 其实原理是一样的。我们可以在http报文后加上我们要执行的恶意代码,再使用end函数截取最后一行,把var_dump函数换成eval函数就可以达到无参函数执行命令了。

session_id()

这种方法就是把恶意代码写在cookie的session_id里面。然后就用session_id()这个函数来读取它,然后它会返回出一个字符串,再用eval函数执行命令。但是,这种方法的前提是我们要开启session才能用session_id(),也就是说,要先session_start();用var_dump函数输出一下

session_id(session_start()),出来的是字符串,我们不需要做任何转换,但是session字符串只能有大小写字母和字符串。但不包含字符,所以我们在session这个地方注入命令就要做一些转换了,那么我们可以将字符串进行16进制编码后再用php中的hex2bin函数解码,执行恶意命令。

无参数RCE_第5张图片

 可以进行eval函数执行注入在PHPSESSID中的命令。我们修改PHPSESSID的值,写入十六进制编码后的恶意代码。

无参数RCE_第6张图片

 我本地环境搞不了,所以借用了师傅的图。大佬博客:RCE篇之无参数rce - 学安全的小白 - 博客园

php读取文件函数

直接用php函数来读取我们想要的文件。

localeconv

localeconv() 函数返回一包含本地数字及货币格式信息的数组。

无参数RCE_第7张图片

乍一看这个函数跟文件读取也没关系啊,但就是这个占不到边的函数也能玩出新花样。它返回的是一个数组,数组的第一行是一个点(.) 这个点就可以代表当前目录了,我们可以用current()函数取出这个点。取出这个点之后,再用scandir函数进行目录遍历。

具体传参:var_dump(scandir(current(localeconv())));就可以遍历当前目录所有文件。

current()

它还有别名pos(),其实都是一样的。

输出数组当前元素的值,每一个数组中都有一个内部的指针,指向他当前的元素。初始指向当前数组中的第一个元素。这个函数不会移动指针,但是有next()函数和prev函数可以移动指针。

无参数RCE_第8张图片

scandir()

列出当前目录的文件和目录,以数组的形式展现出来。

无参数RCE_第9张图片

chdir()

chdir() 函数改变当前的目录。这个函数是是用来跳目录的,有时我们要读取的文件不在当前目录下,所以我们要改变当前目录。比如用chdir('..')来跳回上一级目录。再配合scandir函数遍历任意目录下的文件。

array_reverse()

根据单词意思就知道这个函数的功能就是将数组倒过来。

无参数RCE_第10张图片

 highlight_file()

这个函数用来高亮代码,可以相当于文件读取。相当于show_source()。

getcwd()

这个函数返回当前工作目录。成功则返回当前工作目录,失败则返回FALSE。也需要用scandir函数遍历当前工作目录。

dirname()

这个函数返回路径中的目录名称部分。

无参数RCE_第11张图片

get_defined_vars()

返回由所有已定义变量所组成的数组。

返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。

具体使用参考菜鸟教程

例题

这次用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。

无参数RCE_第12张图片

 读取文件时我们可以用show_source来高亮源代码。上图可知flag.php在第三行,因为数组指针默认是指向第一行,所以我们可以用next()函数和array_reverse()函数让数组指针指向flag.php,,最后高亮源代码。

payload:

?exp=show_source(next(array_reverse(scandir(current(localeconv())))));

最后得出flag.。

无参数RCE_第13张图片

 当然,这并不是唯一的方法。我们还可以自己构造PHPSESSID来通过PHPSESSID来注入命令。

使用session_id(session_start())获取我们构造的恶意PHPSESSID。接着我们可以用print_r或

show_source函数来打印或者高亮源代码。具体操作不再演示。

结尾

php中还有很多像这样的无参函数,多做题,多积累。

参考

RCE篇之无参数rce - 学安全的小白 - 博客园

[GXYCTF2019]禁止套娃 1 &无参数RCE - op_hxc - 博客园

你可能感兴趣的:(#,基础漏洞,php,web安全)