PbootCMS3.2.4代码审计

12 分钟

PbootCMS3.2.4代码审计

存储型XSS

后台页面,修改站点信息处,能够通过POST请求修改一些首页面等各页面的一些描述和展示,包括图片等。

image-20240307092030851

抓个包看看路由,这里的路由跟大部分PHP网站的路由控制是一致的,通过一个参数去控制对应的路由执行方法,p=/Site/mod 表示会来到SiteController控制器的mod方法中。

image-20240307092123748

看下代码,在post中主要是通过$_POST提交的数据接收参数,这里面会对数据进行一些处理,这里的处理方法是通过设置一个数组中的键值来确定对每个数据的不同处理,包括请求的方法、需要的类型,是否必须等。最终来到filter中会通过array_key_exists的形式的取得数据中的键,从而执行不同的case方法,

    public function mod()
    {
        if (! $_POST) {
            return;
        }
        
        $data = array(
            'title' => post('title'),
            'subtitle' => post('subtitle'),
            'domain' => post('domain'),
            'logo' => post('logo'),
            'keywords' => post('keywords'),
            'description' => post('description'),
            'icp' => post('icp'),
            'theme' => basename(post('theme')) ?: 'default',
            'statistical' => post('statistical'),
            'copyright' => post('copyright')
        );
        
        path_delete(RUN_PATH . '/config'); // 清理缓存的配置文件
        if ($this->model->checkSite()) {
            if ($this->model->modSite($data)) {
                $this->log('修改站点信息成功!');
                success('修改成功!', - 1);
            } else {
                location(- 1);
            }
        } else {
            $data['acode'] = session('acode');
            if ($this->model->addSite($data)) {
                $this->log('修改站点信息成功!');
                success('修改成功!', - 1);
            } else {
                location(- 1);
            }
        }
    }

image-20240307093632086

比如里面的一段代码,通过取得d_type的键的值,进入到case中,从而确定必须是那一些类型。

image-20240307093756655

POST跑到filter后,它最终处理完全部数据后会来到escape_string中,而这个方法会通过addslashed和htmlspecialchars对敏感字符进行了转义,为了杜绝XSSsql注入的发生,

image-20240307104235916

在全部处理完成之后,它会进入modSite进而将数据插入到数据库中,真正到获取sql语句准备执行语句进入数据库是这一段,checkkey方法主要是检测Key的,也就是原先提交的Title等地方只允许只包含字母、数字、下划线、点和连字符(破折号),随后通过切割Value前面两个字符和后面两个字符来判断是不是整型进行自增自减处理,如果都不是会直接拼接到了字符串中,最终出现一个完整的字符串赋给了$sql->['table']image-20240307094630132

最终来到了buildSql方法,在这个方法中它是通过str_replace的方式来生成真正的sql处理语句的,通过将$sql->['table']替换%table% %value%等已经规定死的sql语句的值生成真正的sql语句,而这些规定的替换语句,在Model中已经被写好了,不同的方法会寻找不同的语句进行替换。

1709776835821

image-20240307100146101

最终在插入到数据库中时,确实是做了实体编码的转换,包括对单引号转换成实体字符串等,所以这里也基本上不能够考虑存在sql注入的情况,但是它会首页的页面输出的时候,并没有维持这种方式。

image-20240307100244038

在访问首页,首先会通过SELECT *FROM AY_SITE将之前设置的各个标签数据取出,进入到parserSiteLabel 方法中,里面会存在一个Match,通过遍历Match中的值,进入不同的case进行不同的解析,在来到copyright的时候,它会先进行了decode再进入到adjustLabelData处理数据,所以这里前面sql更新时候的转义就没有什么用了。

image-20240307102303391

image-20240307102913942

image-20240307102033500

image-20240307103016213

这里其实有考虑过二次注入的情况,很可惜的是,它SELECT获取出数据后,只是进行了解码渲染了页面,并没有再找到有其它继续进行数据库的操作。

RCE

Pbootcms支持动态缓存的操作,当这个配置开启之后,会对一些页面在Runtime/cacheHTML进行缓存,在Runtime/config会对部分配置文件进行重新的写入缓存,它主要是根据Array中的字段值来进行不同的缓存方法,分别是modDbconfigmodConfigmodConfig的处理主要是通过file_get_contents取出原config.php文件的值,随后通过preg_replace替换后,重新生成随机数写入到Runtime/config中。

image-20240308155702460

image-20240308160333005

当在站点关键字处写入绕过了php的各种限制方法后,访问页面进行缓存的时候,会对页面进行解析,解析会执行copyright里面的php代码。

image-20240308162459176

当开启缓存后,每次访问一个页面的时候都会来到View中,先通过主题是否存在的判断的,进而去判断/Runtime/complile中的文件是否存在,如果不存在,会重新通过compile函数进行解析编译后写入进去,在缓存解析中导致的php代码被执行。

image-20240308172826615

image-20240308163009440

文件上传(暂未找到)

PbootCMS在对文件上传的把控十分严密,这里暂时没有发现,但是说下方法,采取了黑名单+白名单的形式,它在config.php中定义了白名单format,通过数组的形式分成了多文件和单文件处理,但是其实都是进入到handle_upload中。

image-20240307110539506

image-20240307110625036

通过end取出最后一个.后的内容,进行白名单的匹配后,再进行了一轮黑名单的匹配,最后再将文件名重命名了,以静态主路径+日期随机的形式命名了文件存储,所以这里跨越目录的文件上传和直接上传后缀名文件的形式就不可取了。

function handle_upload($file, $temp, $array_ext_allow, $max_width, $max_height, $watermark)
{
   $save_path = DOC_PATH . STATIC_DIR . '/upload';
   $file = explode('.', $file); // 分离文件名及扩展
   $file_ext = strtolower(end($file)); // 获取扩展
   if (! in_array($file_ext, $array_ext_allow)) {
       return $file_ext . '格式的文件不允许上传!';
   }
   $black = array(
       'php',
       'jsp',
       'asp',
       'vb',
       'exe',
       'sh',
       'cmd',
       'bat',
       'vbs',
       'phtml',
       'class',
       'php2',
       'php3',
       'php4',
       'php5'
   );
   if (in_array($file_ext, $black)) {
       return $file_ext . '格式的文件不允许上传!';
   }
   $image = array(
       'png',
       'jpg',
       'gif',
       'bmp'
   );
   $file = array(
       'ppt',
       'pptx',
       'xls',
       'xlsx',
       'doc',
       'docx',
       'pdf',
       'txt'
   );
   if (in_array($file_ext, $image)) {
       $file_type = 'image';
   } elseif (in_array($file_ext, $file)) {
       $file_type = 'file';
   } else {
       $file_type = 'other';
   }
   if (! check_dir($save_path . '/' . $file_type . '/' . date('Ymd'), true)) {
       return '存储目录创建失败!';
   }
   $file_path = $save_path . '/' . $file_type . '/' . date('Ymd') . '/' . time() . mt_rand(100000, 999999) . '.' . $file_ext;
   if (! move_uploaded_file($temp, $file_path)) { // 从缓存中转存
       return '从缓存中转存失败!';
   }
   $save_file = str_replace(ROOT_PATH, '', $file_path); // 获取文件站点路径
   if (is_image($file_path)) {
       if (($reset = resize_img($file_path, $file_path, $max_width, $max_height)) !== true) {
           return $reset;
       }
       if ($watermark) {
           watermark_img($file_path);
       }
   }
   return $save_file;
}
这里是支持压缩文件的上传,所以如果存在能够触发phar的一些函数,再加上反序列化的链子,或者存在文件包含的这种情况能够包含图片,可能都能够进行RCE,先记着。

同样,在它的附属插件UEditor中也做的比较严密,直接从config.json静态设置中获取$config的配置,最终进入到实例化Uploader方法中,Uploader取的数据之后,会对传入的文件获取各种信息。

image-20240308165546956

一般直接本地上传图片主要处理方法是upFile,这里会对文件进行getFileExt操作,getFileExt中的内容是strtolower(strrchr($this->oriName, '.')),获取最后一个.的后缀,进而返回了后缀名,最后通过了in_array判断后缀名是否在白名单之中。

image-20240308165657321

关于标签

PbootCMS定义了很多渲染模板的标签,其中有一部分存在危险的属性,如{pboot:if}{pboot:sql},除此之外,还有很多其它带危险的标签,如下:

  • {pboot:sql} 标签用于在模板中执行 SQL 查询,并将查询结果作为变量存储在模板中,以便在页面中使用。
  • {pboot:eval}:用于执行任意的 PHP 代码,如果不加以限制和过滤,可能导致代码注入漏洞。
  • {pboot:include}:用于包含其他模板文件或外部文件,如果包含的文件不受信任或未经验证,可能导致包含任意文件漏洞。
  • {pboot:php}:类似于 {pboot:eval},用于执行 PHP 代码,同样可能导致代码注入漏洞。
  • {pboot:exec}:用于执行系统命令,如果允许执行任意命令,可能导致命令注入漏洞。
  • {pboot:foreach}:循环标签,如果循环次数不受限制,可能导致服务器资源耗尽或拒绝服务攻击。
  • {pboot:assign}:用于给变量赋值,如果允许动态赋值或者赋值内容不受限制,可能导致变量覆盖或数据篡改。

在上面的post方法中,所有请求的参数都会经过这个参数的处理,在这个方法的下面,会对poboot:ifpboot:sql标签进行了转换,使得标签在代码处理过程中失效,最后在输出到页面的时候又转了回来。

image-20240308101011837

同时在进行数据库的查询,插入等操作的时候,也会出现进一步的替换过滤,这就导致了即使尝试使用数组传参绕过了第一层的过滤,第二层遍历取出的时候,还是会被过滤掉,在使用二层数组的时候,最终取出的值变成了Array,导致了似乎没法绕过,所以只能把目标放在其它的模板方法中。

image-20240308110359930

ParserController.php中,可以看到每次请求页面时,渲染解析的标签,并没有找到能够达到命令执行效果的标签,当然除了先前的if等标签,而且之前所有解析标签进入的eval等能够进行代码执行的函数都已经被去除了。

image-20240308110939302

if标签等等的各种解析,也都是通过正则替换从而写入了php代码的内容达到了渲染的效果。

image-20240308175544868

总结

这次收获不大,整个CMS的过滤也比较强,以后回来再看看吧。

~  ~  The   End  ~  ~


 赏 
承蒙厚爱,倍感珍贵,我会继续努力哒!
logo图像
tips
文章二维码 分类标签:Web安全Web安全
文章标题:PbootCMS3.2.4代码审计
文章链接:https://aiwin.fun/index.php/archives/4391/
最后编辑:2024 年 3 月 13 日 17:31 By Aiwin
许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)
(*) 2 + 4 =
快来做第一个评论的人吧~