freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

ekucms2.5本地文件包含漏洞-代码审计
2022-02-17 01:02:29
所属地 北京

一、前言

为了学习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

image


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

Note:

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

image

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

image

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

image

综上所述,同理查找该文件中使用了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() 函数调用,因此直接排除该文件。

image

(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,至此产生文件包含漏洞的源代码定位成功。

image

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中存在两处-

image

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

image

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

四、总结

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

image

# 渗透测试 # web安全 # 网络安全技术
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录