zoukankan      html  css  js  c++  java
  • PbootCMS2.07前台任意文件包含漏洞(复现)

    0x00

    审计对象:PbootCMS2.07
    cms下载地址:
    https://github.com/hnaoyun/PbootCMS/releases/tag/V2.0.7
    参考文章:

    https://xz.aliyun.com/t/7744#toc-1

    漏洞代码

    漏洞发生在 core/view/view.php

    即解析模板文件的parser函数:

     // 解析模板文件
        public function parser($file)
        {
            // 设置主题
            $theme = isset($this->vars['theme']) ? $this->vars['theme'] : 'default';
            
            $theme = preg_replace('/..(/|\)/', '', $theme); // 过滤掉相对路径
            $file = preg_replace('/..(/|\)/', '', $file); // 过滤掉相对路径
            
            if (strpos($file, '/') === 0) { // 绝对路径模板
                $tpl_file = ROOT_PATH . $file;
            } elseif (! ! $pos = strpos($file, '@')) { // 跨模块调用
                $path = APP_PATH . '/' . substr($file, 0, $pos) . '/view/' . $theme;
                define('APP_THEME_DIR', str_replace(DOC_PATH, '', $path));
                if (! is_dir($path)) { // 检查主题是否存在
                    error('模板主题目录不存在!主题路径:' . $path);
                } else {
                    $this->tplPath = $path;
                }
                $tpl_file = $path . '/' . substr($file, $pos + 1);
            } else {
                // 定义当前应用主题目录
                define('APP_THEME_DIR', str_replace(DOC_PATH, '', APP_VIEW_PATH) . '/' . $theme);
                if (! is_dir($this->tplPath .= '/' . $theme)) { // 检查主题是否存在
                    error('模板主题目录不存在!主题路径:' . APP_THEME_DIR);
                }
                $tpl_file = $this->tplPath . '/' . $file; // 模板文件
            }
            $note = Config::get('tpl_html_dir') ? '<br>同时检测到您系统中启用了模板子目录' . Config::get('tpl_html_dir') . ',请核对是否是此原因导致!' : '';
            file_exists($tpl_file) ?: error('模板文件' . APP_THEME_DIR . '/' . $file . '不存在!' . $note);
            $tpl_c_file = $this->tplcPath . '/' . md5($tpl_file) . '.php'; // 编译文件
                                                                           
            // 当编译文件不存在,或者模板文件修改过,则重新生成编译文件
            if (! file_exists($tpl_c_file) || filemtime($tpl_c_file) < filemtime($tpl_file) || ! Config::get('tpl_parser_cache')) {
                $content = Parser::compile($this->tplPath, $tpl_file); // 解析模板
                file_put_contents($tpl_c_file, $content) ?: error('编译文件' . $tpl_c_file . '生成出错!请检查目录是否有可写权限!'); // 写入编译文件
                $compile = true;
            }
            
            ob_start(); // 开启缓冲区,引入编译文件
            $rs = include $tpl_c_file;
            if (! isset($compile)) {
                foreach ($rs as $value) { // 检查包含文件是否更新,其中一个包含文件不存在或修改则重新解析模板
                    if (! file_exists($value) || filemtime($tpl_c_file) < filemtime($value) || ! Config::get('tpl_parser_cache')) {
                        $content = Parser::compile($this->tplPath, $tpl_file); // 解析模板
                        file_put_contents($tpl_c_file, $content) ?: error('编译文件' . $tpl_c_file . '生成出错!请检查目录是否有可写权限!'); // 写入编译文件
                        ob_clean();
                        include $tpl_c_file;
                        break;
                    }
                }
            }
            $content = ob_get_contents();
            ob_end_clean();
            return $content;
        }
    

    我们可以看到这个函数首先对传过来的参数进行过滤:

    $file = preg_replace('/..(/|\)/', '', $file); // 过滤掉相对路径
    

    这个函数过滤的实际上是过滤2个.和/与(\就是)
    但是却可以绕过,我们本地测试一下:

    这样就得到了后续我们想要的字符。
    继续看代码:
    之后生成模板文件

     $tpl_file = $this->tplPath . '/' . $file; // 模板文件
    

    然后编译文件

    $tpl_c_file = $this->tplcPath . '/' . md5($tpl_file) . '.php'; // 编译文件
    

    之后就到了关键点

    当编译文件不存在,或者模板文件修改过,则重新生成编译文件
    我们可以看到他首先从缓存中判断编译文件存不存在,不存在则解析模板重新生成编译文件。

    file_put_contents这个函数将$content写入,生成编译文件,include $tpl_c_file; 包含我们的编译文件。
    到这里我们的攻击思路已经很明确了,我们需要让parser函数的参数可以被控制,造造成任意文件包含。
    找漏洞执行点,也就是找一个调用了parser函数的地方,且参数可控,比如能让我们传入入上面的目录穿越恶意字符造成任意文件读取。

    漏洞执行点

    前台控制器TagController中的index方法:
    apps/home/controller/tagcontroller.php

    $tagstpl = request('tagstpl');
    

    直接传入导致我们前台$tagstpl可控
    这里::用来直接调用类中的属性或方法
    所以调用parser函数,且直接拼接,那个正则不影响我们的paylaod。
    $content = parent::parser($this->htmldir . $tagstpl); // 框架标签解析
    然后就是虽然这个$content前面被拼接了$this->htmldir,但是函数内部可以出现目录穿越,所以$this->htmldir这个路径并不影响。也就是说他是在生成编译文件时穿越的。

    验证成功

    漏洞修复

    官方在2.08版本修改

    直接对TagController的正则进行了修改,强制限制了后缀名为html。

    总结

    这个漏洞用P3师傅的话来说就是
    PbootCMS2.07内核处理缺陷导致的一个前台任意文件包含漏洞
    他的内核函数在生成编译文件的时候造成任意文件读取,我们只要找到可控参数的parser调用就可以触发漏洞。
    同时这次修复并没有对内核做出调整,所以想要绕过补丁可以往这个思路来找,看看哪里同样调用了可控参数的parser。
    最后,膜P3师傅!

    参考

    https://xz.aliyun.com/t/7744#toc-0

  • 相关阅读:
    J2EE开发环境
    Java核心api
    SCJP (SUN认证Java程序员)
    蓝领”变“金领”
    阿飞正传
    高效项目的七个习惯转载
    写程序的一些感想和教训(转载)
    学习的过程也是迭代的过程
    管理的艺术
    怎样成为优秀的软件模型设计者?[精华]
  • 原文地址:https://www.cnblogs.com/wangtanzhi/p/12930074.html
Copyright © 2011-2022 走看看