跳转至

每日一题 —— [PolarCTF]PHP是世界上最好的语言

题目地址:https://www.polarctf.com/#/page/challenges

打开题目,发现一页的代码,根据提示,flag 在 $flag 变量里,也就是说只需要打开 ./flag.php 就可以了。

接着往下看,key1key2 没有什么用,直接掠过。然后是 isset 函数,表明了你不可以通过 GET 和 POST 方式提交 flag1flag2 参数。

接着往下看,有两行代码,分别是 parse_strextract。他们的本质都是一样的,一个把 GET 的参数进行了解析与变量覆盖,一个把 POST 参数进行了变量覆盖。最后,题目要求我们 flag1flag2 参数都要等于 8gen1 。也就是说这道题的考点之一就是,如何将 flag1 参数传进去,并且成功覆盖。

我们先通过 response 以及 Wappalyzer 插件来获知,这个服务器使用的 php 版本为 7.0.33,也就是说我们的 parse_str 方法可以只用一个参数,把 url 后面的 GET 请求字符串直接解析为 php 变量,而不用先存储在数组中。

我们不难发现,变量覆盖的代码有两行,分别是解析 GET 和解析 POST。既然我们不能通过 GET/POST 方式传入 flag1 ,那么我们能不能把 flag1 从 GET 传给 POST? 经过尝试,意识到 $_POST 本身也是一个变量,数组类型的。那么我们就可以给这个数组类型添加两个元素进去。因此我们的 GET payload 就这么写:

?_POST[flag1]=8gen1&_POST[flag2]=8gen1

在 URL 中传参是可以传入同名变量的,而且如果这个变量名后面跟着中括号的话,这个参数会被视作数组。也就是说,我们传入的 _POST[flag1]=8gen1 就会被解析为 $_POST['flag1'] = '8gen1' ,然后再由第二条 extract($_POST) ,我们就得到了 $flag1 。同理,$flag2 也得到了,并且他们的值都是 8gen1

现在,我们进入了第二步。在这里要求我们的 POST 方法中传入一个 504_SYS.COM 的参数。因为 php 不允许传入非法字符,而 . 号其实就是非法字符之一,所以如果我们直接传参,它就会帮我们转换成 504_SYS_COM 了。这个其实就是 php 的非法传参漏洞。这个漏洞直到 php8 才被修复,所以我们在这里利用这个漏洞:当 php <= 7.4 时,php 只会处理参数名的第一个非法字符。通常我们使用 [ 这个符号。

POST:
504[SYS.COM=1

这样子的话,这个参数就会被转换成 504_SYS.COM ,完成了传参的任务。

接下来的话是最后一步。这是一个 RCE,有一大串符号被过滤。看样子我们需要套个娃。首先先是简单地显示一下当前目录的文件。利用 scandir() 方法来查看当前目录:

POST:
sys=print_r(scandir(getcwd()));

然后就能看到当前目录下的所有文件。

但是我们其实早就知道 flag 在 flag.php 里面了,所以我们需要正经的 RCE。本来还在苦恼无参数 RCE, 但是其实这道题只是一个简单的套娃 RCE. 我们在外层使用 eval 函数,中间一层只需要使用 hex2bin 方法就可以绕过符号的过滤了。至于最内层的 16 进制字符串,我们不能直接写入,因为需要引号包裹。所以我们可以通过变量传入。这里我们借助 GET 方法。

?1=73797374656d282774616320666c61672e70687027293b

POST:
sys=eval(hex2bin($_GET[1]));

其中,参数 1 中的 16 进制字符串编码自 system('tac flag.php');, 也就是反向读取 flag.php 防止浏览器解析。

那么,我们的最终 payload 就是:

?_POST[flag1]=8gen1&_POST[flag2]=8gen1&1=73797374656d282774616320666c61672e70687027293b

POST:
504[SYS.COM=1&sys=eval(hex2bin($_GET[1]));

文章热度:0次阅读