前言
禅道是第一款国产的开源项目管理软件。它集产品管理、项目管理、质量管理、文档管理、 组织管理和事务管理于一体,是一款专业的研发项目管理软件,完整地覆盖了项目管理的核心流程。禅道项目管理系统覆盖了如下各行各业,其中也包含了行业的独角兽,流行范围广,命令执行漏洞的影响面也巨大。
故作此文,希望引起行业重视,积极配合修复漏洞,使用最新版禅道系统。
文章分两篇,上篇介绍如何绕过鉴权,进入禅道后台;下篇介绍禅道后台都有哪些可以深入利用的漏洞。
存在漏洞的版本
禅道开源版:[15.0,18.11]
禅道企业版:[5.0,8.11]
禅道旗舰版:[2.0,4.11]
以下分析均根据开源版。
看我如何进入禅道后台
1. 全域未鉴权漏洞
漏洞成因
鉴权错误未中止程序导致了未鉴权漏洞。
影响范围
[16.5-17.3]。
漏洞分析
先从这里开始了解禅道的鉴权逻辑吧。
禅道的入口很简单,初始化禅道App后,解析参数,检查权限,检查Iframe,然后运行对应的模块和方法。
跟进checkPriv
正常流程通过权限校验有两种。
-
一个是不需要鉴权的模块和方法(白名单),对应 isOpenMethod
-
另一个则是正常登陆后,App记录了用户信息,即存在 $this->app->user
变量。
但是图中可以看到,如果请求满足三点
-
请求不在白名单的方法 -
没有登录(没有 $this->app->user
) -
helper::isAjaxRequest()
返回False
会执行到helper::end()
,抛出异常。
然后异常被捕获,执行echo $endResponseException->getContent();
,此时程序不会中止,继续向下执行,最后调用模块和方法。
登录了的用户的权限检查失败,会调用deny
方法,也会执行到helper::end()
抛出异常。
会产生如下两个问题:
-
未登录用户的未授权漏洞 -
登录用户的越权漏洞
至于16.5这个小版本呢,
https://github.com/easysoft/zentaopms/blob/zentaopms_16.5/module/common/model.php#L2235
在没有登录用户时,直接响应print
,也是一个未登录用户的未授权漏洞。
漏洞修复
https://github.com/easysoft/zentaopms/blob/zentaopms_17.4/module/common/model.php#L2382
将没有用户时,鉴权失败的 help::end()
改成了die
但是!抛出异常还是echo
哦,程序并没有退出。
2. 任意用户登录
漏洞成因
登录用户校验不完善导致黑客利用misc-captcha
接口伪造SESSION和tutorial-wizard
接口暴露用户密码,最终实现任意用户登录。
影响范围
[15.0,16.4]
[17.0-18.1)
漏洞分析
上面的漏洞仅影响当时的较新版禅道,影响范围小,还需要继续深入挖掘禅道的低版本的漏洞。
低版本禅道([15.0,16.4])鉴权方式如下
没有登录直接die
掉,也没有捕获任何异常。所以需要深入isOpenMethod
,看有哪些利用的白名单方法。
我注意到这里的isLogon
的校验并不严谨,仅判断有没有session->user
,还有 user->account != 'guest'
。
绕过的话,只需要找一个可以给session->user
设置值的地方即可,类型无所谓,php弱类型语言,当然这里能绕过,也归功于$this->session
是一个对象类型,原理可以参照如下:
虽然报错了,但仍然返回了true。
所以接下来挖掘的方向变成了 找到一个可以写入任意session的点,键可控或为user,值无关
写简单的正则即可:
-
this->session->set('user' -
this->session->set($w*,
根据这两个正则表达式,可以找到两处可以写入用户的。
deny
和 captcha
在15-17.x 版本中并未见到deny
方法的调用。
captcha
方法则是开放路由可访问的,接收sessionVar
参数,给session->$sessionVar
设置一个随机字符串。
请求http://localhost:8082/index.php?m=misc&f=captcha&sessionVar=user
成功写入session。但是仅有session,还没有用,因为低版本的禅道,鉴权失败会抛出异常的,异常并没有被捕获,所以程序中止。使用伪造的session一定会导致鉴权失败的,所以还需要再isOpenMethod
方法中寻找出路。
我们现在已经通过了isLogon
的校验了,可以执行下面的模块和方法了。
在这些openMethod
,找到 tutorial-wizard
可以进一步利用。
fetch
的逻辑简单来讲,就是后端内部调用接口,而非外部请求。外部请求需要校验权限,而fetch
无需鉴权。
能调用的方法,需要满足以下条件
-
所属模块在 $this->lang->tutorial->tasks
配置内 -
GET请求
可以调用user
模块内的。
user-todo
方法
允许接收一个userId
,查询所有字段信息!
也就是会把密码等字段查询出来,最后渲染整个User
对象
当渲染方式为JSON
时, user对象的所有数据将会暴露,禅道会接受一个查询参数t。
而且已知,禅道是对用户密码做了防护的。
但此处没有,至今仍未修复。当然,有md5加密的密码,也可能存在无法解密的问题。
我在查看禅道的登录逻辑时,发现
禅道是前端对密码进行md5加密,对应了后端密码长度位32的条件。
也就是先对我们的明文密码md5,然后再拼接 rand,再次md5。
md5('21232f297a57a5a743894a0e4a801fc3'+'723913099') = dac363f88d7b1b6459ee12e9fd4dd570
所以,在知道verifyRand
后,我们可以构造出请求包,直接使用数据库的md5密码就可以登录了后台。
漏洞修复
https://github.com/easysoft/zentaopms/blob/zentaopms_18.0/module/common/model.php
禅道在18.0的版本中修复了捕获异常不退出的漏洞。
也就是说在[17.0-18.0) 之间的版本,都存在利用misc-captcha
伪造session,然后捕获异常不退出的漏洞。
https://github.com/easysoft/zentaopms/blob/zentaopms_18.1/module/misc/control.php#L233
漏洞修复比较简单粗暴,直接禁止传入user
但是治标不治本,上面的deny
依然可以创建session->user
。
https://github.com/easysoft/zentaopms/blob/zentaopms_18.1/module/user/model.php#L1203
这里增加了 对$user->account
不为空的校验。
3. 任意用户修改(喧喧
漏洞成因
禅道集成喧喧,下载喧喧配置的接口未鉴权,禅道集成喧喧使用的密钥泄露,导致黑客可以伪造喧喧请求,修改用户密码。
影响版本
[15.3,18.7)
漏洞分析
下载喧喧配置的接口也是一个白名单接口
这个xuanxuan->key
是做什么的呢?
禅道框架的入口文件里,有一个x.php,这个文件是开放给喧喧使用的,使用了禅道框架里一个独立的App类,xuanxuan。
xuanxuan解析请求参数的方式入下,
跟进setInput
方法
从数据库里查aesKey。
然后对请求进行解密。
所以我们可以下载xuanxuan配置,然后伪造xuanxuan请求x.php。
xuanxuan可以使用的方法也是一个白名单,基本只能访问im模块里的方法。
这个模块就是专门给xuanxuan使用的,里面存在用户修改的方法。
后面通过im模块里的模型类使用的__call
魔术方法来控制方法调用的。
user::update
可以修改任意用户密码。
漏洞修复
在18.7版本中,修改了downloadXXD
方法。
https://github.com/easysoft/zentaopms/blob/zentaopms_18.7/xuanxuan/extension/xuan/setting/ext/control/downloadxxd.php#L6
这个逻辑校验比较严格
-
存在登录用户,且存在该接口的权限 -
或者是管理员用户。
4. 任意用户创建/修改(API接口
漏洞成因
API接口处的登录用户校验不完善,和用户权限校验失败不退出的问题,导致黑客可以利用SESSION伪造问题,创建任意管理员用户或修改任意用户密码。
影响范围
[17.8-18.11]
漏洞分析
这个问题由来已久,直到现在才被修复,是与上面第二条的任意用户登录问题一并发现的。
回顾一下上面对于session伪造的修复
-
misc-captcha
禁止传入user值。 -
isLogon
方法增加了登录用户名的判断。
index.php 的入口点显然是被封堵了,继续寻找禅道的其他入口点。
禅道还提供了api.php 用来调用API,也用了一个独立的App类,下面叫他api应用。
api应用,在创建时,会截取入口文件后的第一个路径参数,作为版本。
如果存在版本变量,就不调用 $common->checkPriv()
。
checkPriv
与index.php 的主入口一样,此处不再介绍。
看一下checkEntry
,与checkPriv
一样也是鉴权的。
如果存在版本号,则直接返回checkNewEntry()
的执行结果。否则先检查是否是白名单方法,如果不在白名单里,就从数据库中查询Token,查不到,或者Token不对则返回错误。
跟进一下checkNewEntry方法。
注意这里是return ! 只是退出了当前函数,而不是鉴权失败中止程序继续执行。
所以在请求时加上一个对应版本的路径参数,即可绕过,或者换句话说,api.php入口的鉴权也是失效的。
禅道的API接口定义如下。
路由到的类文件结构如下。
每个文件代表一个模块的实体类,继承于Entry父类,类中的方法对应了HTTP的请求方法。
在父类的构造函数里,禅道做了登录用户的校验。
跟第二个漏洞中登录用户校验一样的配方,一样的味道,一样可以用SESSION伪造来绕过。
至于SESSION伪造的方式,在修复了user-captcha
后,还可以使用 deny方法。
禅道18.1版本后,上线了一个白名单方法。
然后刚好就调用了deny方法。
这一切刚刚好好,天造地设,无缝衔接。
至此,我们可以通过伪造SESSION,然后访问禅道的API接口。
但是禅道模块的API实体类,实际上是获取了对应模块的控制器类。
比如获取用户的API方法里,
在加载控制器时,又检查了一次权限的。
校验失败,返回403。
send方法在低于17.8的版本里是直接退出了。
高于17.8的版本则直接输出未授权,但是程序继续执行不退出。
所以,在高于17.7的版本里,是可以直接未授权访问禅道接口的。禅道接口里 PUT /users/:id
可以修改用户密码,POST /users/
可以创建任意管理员账户。
漏洞修复
在18.12里,禅道做了很多细节上的改动。
-
修复了实体类构造函数里的鉴权不严谨
-
实体类的 checkPriv
权限校验失败直接退出
-
直接移除了 checkNewEntry
对于此漏洞修复得很完善了。
后记
下篇我将对禅道后台各个版本的高危漏洞进行深入分析,请关注公众号。
如果对分析有任何疑问,欢迎留言讨论。
原文始发于微信公众号(天命团队):禅道的前世今生-上
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论