ThinkCMF 任意文件包含/代码执行漏洞

前言

ByteCTF的opensns 到前段时间斗象科技发的ThinkCMF 框架上的任意内容包含漏洞 再到Li4n0师傅发的TinkcmfX 前台任意代码执行分析 都是由于thinkphp模版设计缺陷再加上开发者不恰到的使用导致的。因此在这里拿ThinkCMF做为例子简单分析学习一下。

影响版本:
ThinkCMF X1.6.0
ThinkCMF X2.1.0
ThinkCMF X2.2.0
ThinkCMF X2.2.1
ThinkCMF X2.2.2

任意文件包含

http://127.0.0.1/?a=display&templateFile=README.md 
image.png

ThinkPHP框架约定可以通过 a 参数来指定对应的 action ,配置文件中的默认模块为 Portal 控制器为 Index 
image.png
因此可知调用的控制器为 Portal/Controller/IndexController.class.php 
image.png
由于没有名为 display 的 action ,因此会调用其父类 HomebaseController 的 display 方法
image.png

然后会调用 $this->parseTemplate 处理 $template 参数,当其是一个存在当文件时,直接返回,然后调用父类的 display 方法,也就是 ThinkPHP3 的 display 方法,接着会调用View类的display方法
image.png
最终 View 类的 fetch 方法。
image.png

可以明显的看到if条件中有危险操作,不过我们进不去,ThinkPHP3 中TMPL_ENGINE_TYPE 默认为 Think 。
image.png不过else条件中有一个钩子操作 
image.png
ThinkCMFX/simplewind/Core/Library/Behavior/ParseTemplateBehavior.class.php
image.png
跟进ThinkPHP内置模板引擎类 Template 的 fetch 方法
image.png$this->loadTemplate 方法读取了 $templateFile 文件内容后,将其编译为tpl缓存文件,返回缓存文件名
image.png
最后调用load方法将其include出来。
image.png

而在ByteCTF opensns 那道题中

<?php
public function search()
{
    $keywords=I('post.keywords','','text');

    $modules = D('Common/Module')->getAll();
    foreach ($modules as $m) {
        if ($m['is_setup'] == 1 && $m['entry'] != '') {
            if (file_exists(APP_PATH . $m['name'] . '/Widget/SearchWidget.class.php')) {
                $mod[] = $m['name'];
            }
        }
    }
    $show_search = get_kanban_config('SEARCH', 'enable', $mod, 'Home');

    $this->assign($keywords);
    $this->assign('showBlocks', $show_search);
    $this->display();
}

assign 方法的 $keywords 参数可控
image.png
通过传入数组 keywords[_filename]=/flag 令 $this->tVar[_filename]=/flag 
display()渲染时
$params = array('var'=>$this->tVar,'file'=>$templateFile,'content'=>$content,'prefix'=>$prefix); 

然后继续跟进ThinkPHP内置模板引擎类 Templatefetch 方法
image.png
上面的$this->tVar数组也就是 this->tVar 传入了 Storage::load 
image.png

接着变量覆盖掉了 $_filename ,从而include了出来。
image.png

代码执行

http://127.0.0.1/index.php?a=display&templateFile=README.md&content=%3C?php%20phpinfo();die();
image.png

还是跟上面一样跟入 fetch 方法,
image.png
继续跟进 $this->loadTemplate 
image.png
跟进编译模板文件内容的 $this->compiler 方法
image.png
可以看到直接进行了拼接,虽然过滤了 ?><?php ,在没有定义 THINK_PATH 的情况下退出程序,但是文件包含是通过 index.php 进行访问的,不会进入该if条件,从而可以执行代码,或者也可以 <?=phpinfo();exit();?> 绕过过滤。
image.png

由于漏洞的关键点在thinkphp View 类的 fetch 方法,我们也可以直接调用 fetch 操作
http://127.0.0.1/?a=fetch&content=%3C?=phpinfo();exit(); 
同样的也可以
http://127.0.0.1/?a=fetch&templateFile=README.md 只不过无回显
image.png

修复

原因其实就是 HomebaseController 类中的 fetch、display 方法是public的,可以被用户任意调用
image.png
而thinkphp中为 protected 
image.png

在thinkphp5.1中对此进行了修复
bytectf opensns那个题是由于用户可控assign的参数或者说是display的参数,在正常环境下基本不可能出现。

Referer

TinkcmfX 前台任意代码执行分析
ThinkCMF 框架上的任意内容包含漏洞
ByteCTF opensns