【安全新闻】ekucms2.5本地文件包含漏洞-代码审计

一、前言

为了学习Thinkphp框架的运行原理以及加强自身代码审计能力,所以特意在网上寻找了一个由php编写的CMS漏洞文章,对其进行复现和逆向代码审计。漏洞参考文章如下:

易酷 cms2.5 本地文件包含漏洞 getshell – Oran9e – 博客园 (cnblogs.com)

  • 调试工具:

phpstudy2018+phpstorm+xdebug; Firefox; xdebug helper

  • 工具使用说明:

(1) phpstudy2018中已经内置了适应的xdebug模块,可以根据需要进行自由调整,较为方便。

(2) phpstudy2018中Xdebug模块位置:phpstudy安装路径\phpstudy\PHPTutorial\php\php-{版本}-nts\ext\php_xdebug.dll

(3) 环境配置参考连接:

(32条消息) ThinkPHP学习笔记(七)–PHPstorm+PHPstudy+Xdebug断点调试_limingliang_的博客-CSDN博客_thinkphp断点调试

二、代码审计的思路

(1)利用PHP代码中文件包含存在的高危函数,快速定位那些文件可能存在本地文件包含漏洞。(主要通过全局搜索功能)

(2)在分析文件是否存在本地含漏洞过程中,分析文件关系,确认文件的主要功能点。(静态跟踪函数和变量)

(3)定位可控参数传参点,并使用动态调试,分析数据之间的传递关系。(动态调试)

三、代码审计过程

1.利用关键函数(include)定位可能存在文件包含漏洞的PHP文件,再经过排除从搜索结果中显示文件名已经固定好的文件,最后结果如下:

core/Lib/View.class.php

core/ThinkPHP/Lib/Think/Core/View.class.php

core/ThinkPHP/Lib/Think/Template/ThinkTemplate.class.php

core/ThinkPHP/Common/functions.php

temp/~runtime.php

2.依次定位上述三个文件中的函数位置,并分析逻辑,结果如下:

  • Lib\View.class.php

【安全新闻】ekucms2.5本地文件包含漏洞-代码审计

通过分析上述代码发现,该函数在调试时使用,同时由于开头已经定义了包含文件名($traceFile = CONFIG_PATH.’trace.php’;),因此不存在可输入点,可以直接跳过。

Note:

(1) 经过追踪变量CONFIG_PATH,发现该变量在(core/ThinkPHP/Common/paths.php)已经定义。

【安全新闻】ekucms2.5本地文件包含漏洞-代码审计1

(3) 变量 APP_PATH定位位置:core/Conf/define.php

【安全新闻】ekucms2.5本地文件包含漏洞-代码审计2

(3) 变量 CONF_DIR定义的位置:core/ThinkPHP/Common/paths.php

【安全新闻】ekucms2.5本地文件包含漏洞-代码审计3

综上所述,同理查找该文件中使用了include其他的函数,最后发现未使用固定的文件路径的函数为:fetch()

代码如下:

public function fetch($templateFile='',$charset='',$contentType='text/html',$display=false)
    {
        $GLOBALS['_viewStartTime'] = microtime(TRUE);
        if(null===$templateFile)
            // 使用null参数作为模版名直接返回不做任何输出
            return ;
        if(empty($charset))  $charset = C('DEFAULT_CHARSET');
        // 网页字符编码
        header("Content-Type:".$contentType."; charset=".$charset);
        header("Cache-control: private");  //支持页面回跳
        //页面缓存
        ob_start();
        ob_implicit_flush(0);

        if(!file_exists_case($templateFile))
            // 自动定位模板文件
            $templateFile   = $this->parseTemplateFile($templateFile);

        $engine  = strtolower(C('TMPL_ENGINE_TYPE'));
        if('php'==$engine) {
            // 模板阵列变量分解成为独立变量
            extract($this->tVar, EXTR_OVERWRITE);
            // 直接载入PHP模板
            include $templateFile;
        }elseif('think'==$engine && $this->checkCache($templateFile)) {
            // 如果是Think模板引擎并且缓存有效 分解变量并载入模板缓存
            extract($this->tVar, EXTR_OVERWRITE);
            //载入模版缓存文件
            include C('CACHE_PATH').md5($templateFile).C('TMPL_CACHFILE_SUFFIX');
        }else{
            // 模板文件需要重新编译 支持第三方模板引擎
            // 调用模板引擎解析和输出
            $className   = 'Template'.ucwords($engine);
            require_cache(THINK_PATH.'/Lib/Think/Util/Template/'.$className.'.class.php');
            $tpl   =  new $className;
            $tpl->fetch($templateFile,$this->tVar,$charset);
        }
        $this->templateFile   =  $templateFile;
        // 获取并清空缓存
        $content = ob_get_clean();
        // 模板内容替换
        $content = $this->templateContentReplace($content);
        // 布局模板解析
        $content = $this->layout($content,$charset,$contentType);
        // 输出模板文件
        return $this->output($content,$display);
    }

通过分析以上代码逻辑,出现以下结论:

(1)该函数存在接收变量:$templateFile

(2)该函数在接收变量的参数值时,不存在严格过滤行为

(3)该函数中存在 include $templateFile 操作

因此可以判断该函数存在本文件包含漏洞,需要进一步跟踪该函数被调用的位置,同时确认 $templateFile 变量是否能够由URL中获取。

经过查找,发现 $templateFile 变量在该文件中被定义,但未找到该项目中,调用该文件下View类的方法。(后续分析发现,是源代码有过改动,因此不能利用。)

3.通过上述结论,并查找项目中所有代码发现,开发者相同的函数存在多处定义的情况。因此直接利用函数 fetch() 查找代码,最终发现如下文件中,也存在相同的函数定义和调用:

(1)temp/~runtime.php

(2)core/ThinkPHP/Lib/Think/Core/Action.class.php

(1)经过分析发现 core/ThinkPHP/Lib/Think/Core/Action.class.php 文件中不存在 fetch() 函数调用,因此直接排除该文件。

【安全新闻】ekucms2.5本地文件包含漏洞-代码审计4

(2)分析 temp/~runtime.php 的源代码中的fecth()函数。

public function fetch($templateFile='',$charset='',$contentType='text/html',$display=false) {
        $GLOBALS['_viewStartTime'] = microtime(TRUE);
        if(null===$templateFile) return ;
        if(empty($charset)) $charset = C('DEFAULT_CHARSET');
        header("Content-Type:".$contentType."; charset=".$charset);
        header("Cache-control: private");
        ob_start();
        ob_implicit_flush(0);
        if(!file_exists_case($templateFile)) $templateFile = $this->parseTemplateFile($templateFile);
        $engine = strtolower(C('TMPL_ENGINE_TYPE'));
        if('php'==$engine) {
            extract($this->tVar, EXTR_OVERWRITE);
            include $templateFile;
        } elseif('think'==$engine && $this->checkCache($templateFile)) {
            extract($this->tVar, EXTR_OVERWRITE);
            include C('CACHE_PATH').md5($templateFile).C('TMPL_CACHFILE_SUFFIX');
        } else {
            $className = 'Template'.ucwords($engine);
            require_cache(THINK_PATH.'/Lib/Think/Util/Template/'.$className.'.class.php');
            $tpl = new $className;
            $tpl->fetch($templateFile,$this->tVar,$charset);
        }
        $this->templateFile = $templateFile;
        $content = ob_get_clean();
        $content = $this->templateContentReplace($content);
        $content = $this->layout($content,$charset,$contentType);
        return $this->output($content,$display);
    }

通过分析代码能发现存在两处本地文件包含漏洞位置;

if('php'==$engine) {
            extract($this->tVar, EXTR_OVERWRITE);
            include $templateFile;
        } elseif('think'==$engine && $this->checkCache($templateFile)) {
            extract($this->tVar, EXTR_OVERWRITE);
            include C('CACHE_PATH').md5($templateFile).C('TMPL_CACHFILE_SUFFIX');
        } else {
            $className = 'Template'.ucwords($engine);
            require_cache(THINK_PATH.'/Lib/Think/Util/Template/'.$className.'.class.php');
            $tpl = new $className;
            $tpl->fetch($templateFile,$this->tVar,$charset);
        }
  • include $templateFile;

经过分析代码逻辑发现,该处由于自定义变量 TMPL_ENGINE_TYPE=think,不满足 **if(‘php’==$engine)**判定条件,因此无法调用。

  • include C(‘CACHE_PATH’).md5($templateFile).C(‘TMPL_CACHFILE_SUFFIX’)

1)通过继续追踪代码逻辑,需要满足调用 checkCache($templateFile) 的条件才能使用include,接下来继续追踪分析分析 checkCache($templateFile)

protected function checkCache($tmplTemplateFile) {
        if (!C('TMPL_CACHE_ON')) return false;
        $tmplCacheFile = C('CACHE_PATH').md5($tmplTemplateFile).C('TMPL_CACHFILE_SUFFIX');
        if(!is_file($tmplCacheFile)) {
            return false;
        } elseif (filemtime($tmplTemplateFile) > filemtime($tmplCacheFile)) {
            return false;
        } elseif (C('TMPL_CACHE_TIME') != -1 && time() > filemtime($tmplCacheFile)+C('TMPL_CACHE_TIME')) {
            return false;
        }
        return true;
    }

通过最终追踪分析 C(‘CACHE_PATH’)=/temp/CacheC(‘TMPL_CACHFILE_SUFFIX’)=html,得到变量 $tmplCacheFile=/temp/Cache/模板名称.html,并判断缓存文件是否存在;若缓存文件不存在,则放回True;若存在缓存文件,且缓存文件的修改时间要小于保存的时间,则返回FALSE。(在进一步的动态调试测试分析过程中,发现TMPL_CACHE_ON 变量经过处理后一直为 OFF状态,因此会直接跳过。)

2)返回fetch()函数,继续分析,最终定位到可利用代码块如下:

a.不存在缓存文件的情况,且 TMPL_CACHE_ON在传入是为 ON 状态:

...
    extract($this->tVar, EXTR_OVERWRITE);
    include C('CACHE_PATH').md5($templateFile).C('TMPL_CACHFILE_SUFFIX');
...

可以直接通过该值进行利用。(PS:这个时候与ThinkPHP3.X~5.X 存在的本地包含漏洞原理一致)

b.存在缓存文件的情况:

...
	$className = 'Template'.ucwords($engine);
	require_cache(THINK_PATH.'/Lib/Think/Util/Template/'.$className.'.class.php');
	$tpl = new $className;
	$tpl->fetch($templateFile,$this->tVar,$charset);
...

进一步跟踪代码,发现是调用并加载模板文件的过程,同时发现调用了 core/ThinkPHP/Lib/Think/Util/Template/TemplateThink.class.php文件中的 **load()**函数。通过进一步跟进函数,发现 load() 函数中存在 文件包含漏洞代码:include $templateCacheFile,至此产生文件包含漏洞的源代码定位成功。

【安全新闻】ekucms2.5本地文件包含漏洞-代码审计5

4.定位参数传递的位置

前提说明:
	由于通过观察源代码项目结构很容易发现,ekucms2.5是利用了thinkphp的MVC(即:Moudle,View,Controller)模式,其传输的URL参数格式有三种:
	(1)http://{IP:port}/index.php?a=test1&b=test2
	(2)http://{IP:port}/index.php/{moudle}/{Controller}/变量/变量值
	(3)http://{IP:port}/index.php/?s={moudle}/{Controller}/变量/变量值 
通过观察发现,其传输参数为第三种,因此只需要寻找到调用 TemplateThink.class.php 文件的模块和控制器即可

(1)首先依据thinkphp的目录结构,寻找配置文件,查看那些模块能够从HTTP报文中获取参数值。通过搜索最终发现 config.php中存在两处-

【安全新闻】ekucms2.5本地文件包含漏洞-代码审计6

分别为:/home/info/detail 和 /home/my/show,依次访问相应路径的网页,查看是否正常触发断点。最终确认 /home/my/show模块能够能够向 参数 id传入值。

【安全新闻】ekucms2.5本地文件包含漏洞-代码审计7

处理id值的文件位置为:core/Lib/Action/Home/MyAction.class.php

四、总结

ekucms2.5底层采用thinkphp5的框架编写,本次本地文件包含漏洞本质上与thinkphp5.x的产生的原因基本一致。在进行漏洞利用时,程序运行的流程图参考如下:

【安全新闻】ekucms2.5本地文件包含漏洞-代码审计8

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
大佬不来一句? 抢沙发

请登录后发表评论