Funnnny Things.

PbootCMSv1.4.1 Template Injection Getshell

Vulnerability Description

    The latest version of PbootCMS V1.4.1 has vulnerabilities in parsing if statements in templates, resulting in the attacker’s ability to contaminate template content by searching for page contamination URLs, thus triggering vulnerabilities when the program uses eval statements to parse templates.

Vulnerable Packages

    PbootCMS v1.4.1 and previous versions

Credits

    These vulnerabilities were discovered and researched by stick.wang@ZionLab from DBAppSecurity.

Technical Description / Proof of Concept Code

Vulnerable cgi:/apps/home/controller/ParserController.php

    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 we can control the value of $matches [1][$i], we can control the eval function, so let’s see what filters are ahead.     First, the program uses a rule to match the value of the function in $matches [1][$i]. Then it uses two conditions to judge whether the function is dangerous. The first condition is that it cannot be defined by the program or that there is an eval keyword. The second condition is that the value of the function must be in the white list of $white_fun. If one of the two conditions violates the law, it will be recognized as a dangerous function, and the if condition will not be resolved.     In the second condition, we can’t get around the whitelist, so we can only start with the first condition.     In the first condition, if we can make the matched value not a function name, then we can simply bypass it. From the point of view of regular expressions, it should match the value before (), but in php, variable functions are allowed, that is, $a=”phpinfo”; $a(); this is executable, and if regular expressions are used, only matching a, which of course will not be a defined function, and there will be no Eval keyword.     Looking up, we can see that $matches [1] [$i] is a regular matching of $content. From the analysis of $pattern, we only need to change payload to {pboot: if} {/ pboot: if}, and then restoreLabel method and decode_string method will not affect the specific results.

    // 解析全局后置公共标签
    public function parserAfter($content)
    {
        $content = $this->parserSingleLabel($content); // 单标签解析
        $content = $this->parserSiteLabel($content); // 站点标签
        $content = $this->parserCompanyLabel($content); // 公司标签
        $content = $this->parserUserLabel($content); // 自定义标签
        $content = $this->parserNavLabel($content); // 分类列表
        $content = $this->parserSelectAllLabel($content); // CMS筛选全部标签解析
        $content = $this->parserSelectLabel($content); // CMS筛选标签解析
        $content = $this->parserSpecifySortLabel($content); // 指定分类
        $content = $this->parserListLabel($content); // 指定列表
        $content = $this->parserSpecifyContentLabel($content); // 指定内容
        $content = $this->parserContentPicsLabel($content); // 内容多图
        $content = $this->parserContentCheckboxLabel($content); // 内容多选调取
        $content = $this->parserContentTagsLabel($content); // 内容tags调取
        $content = $this->parserSlideLabel($content); // 幻灯片
        $content = $this->parserLinkLabel($content); // 友情链接
        $content = $this->parserMessageLabel($content); // 留言板parserQrcodeLabel
        $content = $this->parserFormLabel($content); // 自定义表单
        $content = $this->parserSubmitFormLabel($content); // 自定义表单提交
        $content = $this->parserQrcodeLabel($content); // 二维码生成
        $content = $this->parserPageLabel($content); // CMS分页标签解析(需置后)
        $content = $this->parserLoopLabel($content); // LOOP语句(需置后)
        $content = $this->parserIfLabel($content); // IF语句(需置最后)
        $content = $this->restorePreLabel($content); // 还原不需要解析的内容
        return $content;
    }

    We find that this is actually a process of parsing template files. If we want to trigger Eval functions, we must control the content of template files. If we want to trigger front-end, we must have front-end interaction. In fact, in this cms, there are only search and message boards at the front end. We can input the corresponding data. When searching, we can find that there exists filtering of variables, but we can add #, after which we can add other characters. There is a very critical place, that is, when the page generates two-dimensional code, there will be a “sweep mobile phone access” field, which stores the address we visited. image     So we can use this point to insert malicious code into content. But we also find that after generating two-dimensional codes, the address content is encoded by url, which leads to the failure of regular matching when parsing IF.     Enter the parserQrcodeLabel function

    // 解析二维码生成标签
    public function parserQrcodeLabel($content)
    {
        $pattern = '/\{pboot:qrcode(\s+[^}]+)?\}/';
        if (preg_match_all($pattern, $content, $matches)) {
            $count = count($matches[0]);
            for ($i = 0; $i < $count; $i ++) {
                $params = $this->parserParam($matches[1][$i]);
                $string = '';
                foreach ($params as $key => $value) {
                    switch ($key) {
                        case 'string':
                            $string = $value;
                            break;
                    }
                }
                if (! $string) { // 无内容不解析
                    continue;
                }
                $content = str_replace($matches[0][$i], '<img src="' . CORE_DIR . '/qrcode.php?string=' . urlencode($string) . '" class="qrcode" alt="二维码">', $content);
            }
        }
        return $content;
    }

    If we can see that after entering this method, we will perform URLEncode operation on the regularly matched values, then can we change the corresponding values so that the key parts after the regularly mismatched can not be coded?     From a regular expression point of view, it matches the value in {pboot: qrcode} and the content value during debugging is as follows image **    If we add } after aaaa#, then the rule will only match the content before the new }, thus avoiding encoding our data. **

POC:

image




Summary

    In fact, from the code, we can see that the official has been very strict against eval, but there is still a problem of inadequate filtering, in conjunction with the malicious template content imported from the front desk, resulting in this vulnerability. In fact, the official can try to use the white list method to parse the template content, which should be more secure.


Report Timeline


Disclaimer

The author is not responsible for any misuse of the information contained herein and accepts no responsibility for any damage caused by the use or misuse of this information. The author prohibits any malicious use of security related information or exploits by the author or elsewhere.