禅道项目管理系统存在身份认证绕过漏洞,远程攻击者利用该漏洞可以绕过身份认证,调用任意API接口并修改管理员用户的密码,并以管理员用户登录该系统,配合其他漏洞进一步利用后,可以实现完全接管服务器
漏洞描述
禅道项目管理系统存在身份认证绕过漏洞,远程攻击者利用该漏洞可以绕过身份认证,调用任意API接口并修改管理员用户的密码,并以管理员用户登录该系统,配合其他漏洞进一步利用后,可以实现完全接管服务器
影响版本:
16.x <= 禅道项目管理系统< 18.12(开源版)
6.x <= 禅道项目管理系统< 8.12(企业版)
3.x <= 禅道项目管理系统< 4.12(旗舰版)
漏洞复现
根据网上的poc进行复现
首先请求接口
/api.php?m=testcase&f=savexmindimport&HTTP_X_REQUESTED_WITH=XMLHttpRequest,获取到一个cookie
然后在请求头中设置cookie请求/api.php/v1/users接口,响应为error: no company-browse priv.说明存在漏洞,如果不存在漏洞返回的是Unauthorized
分析过程
官方发布的最新版本中,在entry.class.php中对鉴权方式进行了修改
https://github.com/easysoft/zentaopms/commit/d13ba70016ca981b08f27e08fb934bf1f23a4135
当用户发起http请求的时候,程序首先会启动一个app实例,接着检查是否有权限,最后才是执行对应模块下面的方法
由于这个一个利用cookie进行认证绕过的漏洞,首先跟进一下程序的执行过程,看看在哪里创建了cookie
在api.php中,程序首先创建一个app实例api
$app = router::createApp('pms', dirname(dirname(\_\_FILE\_\_)), 'api');
调用了createApp(),程序会执行到router.class.php#startSession()下,在startSession()下断点,该方法会生成一个sessionID
得到一个app实例后,接着会执行api.php下的$common->checkEntry()进行权限的验证,在isOpenMethod()中会进行白名单验证,testcase.savexmindimport在白名单范围内,所以会跳过后续的验证,开始执行testcase控制器下的savexmindimport方法
验证请求合法后会导入testcase模块,执行testcase模块下control.php控制器文件的构造方法,在构造方法中会调用Helper:isAjaxRequest()来判断用户请求是否是AJAX请求,判断是否是AJAX请求主要通过判断HTTP_X_REQUESTED_WITH是否等于XMLHttpRequest来实现,我们可以通过GET请求将HTTP_X_REQUESTED_WITH的值传入进去
如果判断出不是AJAX请求的话,程序就会执行后面的return语句提前结束程序
通过上面的分析我们得到了cookie的创建流程,下面来分析一下利用cookie绕过验证的过程
得到cookie之后就添加到请求头中访问/api.php/v1/users,程序的权限验证代码在entry.class.php下,这段代码会判断this->app->user的值是否存在和account是否等于guest
我们在这行代码下断点后,可以看到添加了cookie请求之后this->app->user的值是存在的,而且this->app-user->account不等于guest,所以继续执行下面的代码
现在再看看官方修复版本之后的代码变成了
if(!$this->loadModel('user')->isLogon()) throw EndResponseException::create($this->sendError(401, 'Unauthorized'));
跟进到user\model.php#isLogon()下,这里 u s e r 的值通过 user的值通过 user的值通过this->session->user来获取,而在没登录的情况下session->user的值是为空的
关于程序创建cookie和权限验证的代码已经分析过了,现在来看看能为什么能绕过,当我们直接访问主页的时候也能获取到一个cookie,我们先看看里面的内容是什么
在本地电脑上找到sess_774fhsc4am826jarqraqab5fis的这个文件,这是正常访问的session,再找一个通过漏洞获取到的看看
下图中上面的是正常访问获取的session,下面的是通过访问poc获得的,可以看到里面最大的区别就是下面的多了一个user字段,所以可以看出认证绕过的原因可能是testcase.savexmindimport()方法下某段代码在session中添加了一个user字段,最终导致绕过的!
那我们跟进到testcase\control.php#saveXmindImport()中,可以看到该方法下调用了common\model.php#deny()
跟进deny(),该方法下会先判断 r e l o a d 的值,这里 reload的值,这里 reload的值,这里reload默认为true,会执行if语句下的代码,代码中会在 t h i s − > a p p 下添加一个 u s e r 字段,关于字段的值是什么不用关心,因为验证的只是 this->app下添加一个user字段,关于字段的值是什么不用关心,因为验证的只是 this−>app下添加一个user字段,关于字段的值是什么不用关心,因为验证的只是this->app->user是否为空,最后就会内容将值存入到session里
所以调用到deny()的都可以实现认证绕过,例如testcase.getxmindimport也调用了deny()