zoukankan      html  css  js  c++  java
  • 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框架的特性,可以通过这种形式的路由方式进行访问相应的功能点,例如通过gma参数指定分组控制器方法

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

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

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

    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(
                'BehaviorParseTemplateBehavior', // 模板解析 支持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:QMDownloadPHPTutorialwww	hinkcmfx2data
    untimeCachePortal
                }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语句中的,我们可以去看下ThinkTemplate的类

        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

  • 相关阅读:
    Socket基本介绍和实际应用
    自定义流水布局(UICollectionViewFlowLayout的基本使用)
    UIMenuController 简单示例 (Swift)
    继续坚持
    获取手机信息(UIDevice、NSBundle、NSLocale)
    iOS 远程推送 根据后台推送内容的不同跳转指定页面
    定制多样式二维码
    二维码扫描和应用跳转
    iOS开发网络篇—Socket编程
    iOS开发中常用英语单词和句子整理(持续更新)
  • 原文地址:https://www.cnblogs.com/zpchcbd/p/11949672.html
Copyright © 2011-2022 走看看