前言
从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
ThinkPHP框架约定可以通过 a
参数来指定对应的 action
,配置文件中的默认模块为 Portal
控制器为 Index
因此可知调用的控制器为 Portal/Controller/IndexController.class.php
由于没有名为 display
的 action
,因此会调用其父类 HomebaseController
的 display
方法
然后会调用 $this->parseTemplate
处理 $template
参数,当其是一个存在当文件时,直接返回,然后调用父类的 display
方法,也就是 ThinkPHP3
的 display
方法,接着会调用View
类的display方法
最终 View
类的 fetch
方法。
可以明显的看到if条件中有危险操作,不过我们进不去,ThinkPHP3
中TMPL_ENGINE_TYPE
默认为 Think
。
不过else条件中有一个钩子操作
ThinkCMFX/simplewind/Core/Library/Behavior/ParseTemplateBehavior.class.php
跟进ThinkPHP内置模板引擎类 Template
的 fetch
方法
$this->loadTemplate
方法读取了 $templateFile
文件内容后,将其编译为tpl缓存文件,返回缓存文件名
最后调用load方法将其include出来。
而在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
参数可控
通过传入数组 keywords[_filename]=/flag
令 $this->tVar[_filename]=/flag
display()渲染时
$params = array('var'=>$this->tVar,'file'=>$templateFile,'content'=>$content,'prefix'=>$prefix);
然后继续跟进ThinkPHP内置模板引擎类 Template
的 fetch
方法
上面的$this->tVar
数组也就是 this->tVar
传入了 Storage::load
接着变量覆盖掉了 $_filename
,从而include了出来。
代码执行
http://127.0.0.1/index.php?a=display&templateFile=README.md&content=%3C?php%20phpinfo();die();
还是跟上面一样跟入 fetch
方法,
继续跟进 $this->loadTemplate
跟进编译模板文件内容的 $this->compiler
方法
可以看到直接进行了拼接,虽然过滤了 ?><?php
,在没有定义 THINK_PATH
的情况下退出程序,但是文件包含是通过 index.php
进行访问的,不会进入该if条件,从而可以执行代码,或者也可以 <?=phpinfo();exit();?>
绕过过滤。
由于漏洞的关键点在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
只不过无回显
修复
原因其实就是 HomebaseController
类中的 fetch、display
方法是public的,可以被用户任意调用
而thinkphp中为 protected
在thinkphp5.1中对此进行了修复
bytectf opensns那个题是由于用户可控assign的参数或者说是display的参数,在正常环境下基本不可能出现。