文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

禅道系统权限绕过与命令执行漏洞分析

2023-09-07 08:00

关注

漏洞概述

禅道是第一款国产的开源项目管理软件,也是国内最流行的项目管理软件。该系统在2023年初被爆出在野命令执行漏洞,官方已于2023年1月12日发布了漏洞修复补丁。该漏洞是由于禅道项目管理系统权限认证存在缺陷导致,攻击者可利用该漏洞在未授权的情况下,通过权限绕过在服务器执行任意命令。

本文以安全研究为目的,分享对该漏洞的研究和复现过程,仅供学习和参考。由于传播、利用此文档提供的信息而造成任何直接或间接的后果及损害,均由使用者本人负责,文章作者不为此承担任何责任。

影响范围

禅道系统

影响版本

开源版

17.4以下的未知版本<=version<=18.0.beta1

旗舰版

3.4以下的未知版本<=version<=4.0.beta1

企业版

7.4以下的未知版本<=version<=8.0.beta1 8.0.beta2

复现环境

操作系统:macOS 13.1

运行环境:nginx1.5 php7.4 mysql5.7

软件版本:zentaopms-zentaopms_18.0.beta1

权限绕过-漏洞分析

权限绕过的关键点在module/common/model.php文件中checkPriv函数,此函数是检查权限的函数,验证当前登陆用户是否有访问module与method的权限。分析代码后得知在没有访问权限时会抛出异常,但是代码中并没有终止程序,只是输出权限不足的内容。具体代码如下:

1

public function checkPriv(){
try

{

$module = $this->app->getModuleName();

$method = $this->app->getMethodName();

if($this->app->isFlow)

{

$module = $this->app->rawModule;

$method = $this->app->rawMethod;

}

$beforeValidMethods = array(
'user' => array('deny', 'logout'),

'my' => array('changepassword'),

'message' => array('ajaxgetmessage'),

);

if(!empty($this->app->user->modifyPassword) and (!isset($beforeValidMethods[$module]) or !in_array($method, $beforeValidMethods[$module]))) return print(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(in_array($module, $this->config->programPriv->waterfall) and $this->app->tab == 'project' and $method != 'browse') return true;

$this->app->user = $this->session->user;
if(!commonModel::hasPriv($module, $method))

{

if($module == 'story' and !empty($this->app->params['storyType']) and strpos(",story,requirement,", ",{$this->app->params['storyType']},") !== false) $module = $this->app->params['storyType'];

$this->deny($module, $method);

}

}

else

{

$uri = $this->app->getURI(true);

if($module == 'message' and $method == 'ajaxgetmessage')

{

$uri = helper::createLink('my');

}

elseif(helper::isAjaxRequest())

{

die(json_encode(array('result' => false, 'message' => $this->lang->error->loginTimeout))); // Fix bug #14478.
}
$referer = helper::safe64Encode($uri);
die(js::locate(helper::createLink('user', 'login', "referer=$referer")));

}

}

catch(EndResponseException $endResponseException)

{

echo $endResponseException->getContent();

}

}

其中commonModel::hasPriv()函数是内置公共的验证权限,代码中可以看出无权限访问就会执行deny 方法,而deny 最后验证的结果是无权限则执行helper::end(),该方法是直接抛出异常,就会进入上面的try cache逻辑。

1

2

3

4

public static function end($content = '')

{

throw EndResponseException::create($content);

}

在进入权限检查的流程前需要在$this->app->user 不为空的情况下将 $this->session->user赋值给 $this->app->user ,然后再做权限检查。因此我们还需要构造一个$this->session->user,即写一个session['user']才能进行绕过。所以现在思路很清晰了,只需$this->session->user 存在就可以通过⽤户是否登录的检查,使权限检查的函数如同虚设。 根据这个思路逆推可以得出结论:只要有任意⼀个⽤户session就可以调⽤任意模块的任意⽅法。

经过代码审计发现captcha函数可以直接写入一个自定义key的session,此段代码本意是设置生成一个自定义session的key的验证码,开发者应该是想写一个公共的验证码生成函数让其他开发者做新功能需要的时候可以直接调用,正好可以利用生成一个key为user的session。

1

public function captcha($sessionVar = 'captcha', $uuid = '')
{

$obLevel = ob_get_level();

for($i = 0; $i < $obLevel; $i++) ob_end_clean();

header('Content-Type: image/jpeg');
$captcha = $this->app->loadClass('
captcha');

$this->session->set($sessionVar, $captcha->getPhrase());

$captcha->build()->output();

}

通过上述思路可以成功实现权限绕过,不过经过实际测试发现,能绕过访问的皆为公共模块。因为在禅道的功能权限验证中还有一部分是验证userid或level。就好比某些用户有“项目1”的权限,某些用户有“项目2”的权限,所以类似这类的数据任然不能访问获取。

命令执行-漏洞分析

实际上整个利用链最关键的一环就在上面的权限绕过上,禅道系统后台本身存在多个sql注入及命令执行漏洞,本文给出一种后台命令执行的方法供参考,其他利用点感兴趣的小伙伴可自行研究。

在权限绕过后,接下来我们需要分析后台命令执行点的位置。通过代码审计,最终锁定在module/repo/model.php文件,其中checkConnection函数会进行SCM=Subversion判断,$client是导致命令注入的参数点,一条完整的函数间调用的利用过程如下所示:

module/repo/model.php->create()

module/repo/control.php->edit ()

module/repo/model.php->update($repoID)->checkConnection()->exec($versionCommand,$versionOutput, $versionResult);

PS:为什么要创建仓库,因为在查看checkConnection调用函数为create和update,但是在create的时候必须经过checkClient 的判断,必须要创建一个文件才行,如果SCM指定为Gitlab就不需要通过checkClient判断。

具体复现思路如下:

1.进入创建仓库的函数:module/repo/model.php

1

public function create(){
if(!$this->checkClient()) return false;

if(!$this->checkConnection()) return false;

$isPipelineServer = in_array(strtolower($this->post->SCM), $this->config->repo->gitServiceList) ? true : false;
$data = fixer::input('post')
->setIf($isPipelineServer, 'password', $this->post->serviceToken)

->setIf($this->post->SCM == 'Gitlab', 'path', '')

->setIf($this->post->SCM == '
Gitlab', 'client', '')

->setIf($this->post->SCM == '
Gitlab', 'extra', $this->post->serviceProject)

->setIf($isPipelineServer, 'prefix', '')

->setIf($this->post->SCM == '
Git', 'account', '')

->setIf($this->post->SCM == '
Git', 'password', '')

->skipSpecial('
path,client,account,password')

->setDefault('product', '')

->join('
product', ',')

->setDefault('projects', '')->join('projects', ',')

->get();

$data->acl = empty($data->acl) ? '' : json_encode($data->acl);

if($data->SCM == '
Subversion')

{

$scm = $this->app->loadClass('scm');

$scm->setEngine($data);

$info
= $scm->info('');

$infoRoot = urldecode($info->root);

$data->prefix = empty($infoRoot) ? '
' : trim(str_ireplace($infoRoot, '', str_replace('\\', '/', $data->path)), '/');

if($data->prefix) $data->prefix = '
/' . $data->prefix;

}

当SCM类型指定为Subversion时,后续控制$client才可以完成命令注入。

2.编辑代码仓库进入module/repo/control.php中的edit函数,post传参会进入到update函数。

1

public function edit($repoID, $objectID = 0){
$this->commonAction($repoID, $objectID);

$repo = $this->repo->getRepoByID($repoID);
if($_POST)

{

$noNeedSync = $this->repo->update($repoID);

if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError()));

$newRepo = $this->repo->getRepoByID($repoID);

$actionID = $this->loadModel('action')->create('repo', $repoID, 'edited');

$changes = common::createChanges($repo, $newRepo);

$this->action->logHistory($actionID, $changes);

跟踪update函数到module/repo/model.php,需要将scm设置为Subversion,此时会去检测svn服务器是否可以连接。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

public function update($id){

$repo = $this->getRepoByID($id);

if(!$this->checkConnection()) return false;

$isPipelineServer = in_array(strtolower($this->post->SCM),$this->config->repo->gitServiceList) ? true : false;

$data = fixer::input('post')

->setIf($isPipelineServer, 'password', $this->post->serviceToken)

->setIf($this->post->SCM == 'Gitlab', 'path', '')

->setIf($this->post->SCM == 'Gitlab', 'client', '')

->setIf($this->post->SCM == 'Gitlab', 'extra', $this->post->serviceProject)

->setDefault('prefix', $repo->prefix)

->setIf($this->post->SCM == 'Gitlab', 'prefix', '')

->setDefault('client', 'svn')

->setDefault('product', '')

->skipSpecial('path,client,account,password')

跟踪该函数,$this->post->SCM等于Subversions时会去check svn服务器version,此刻会把$this->post->client拼接到执行的versionCommand 中,造成命令执行。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

if(empty($_POST)) return false;

$scm

= $this->post->SCM;

$client

= $this->post->client;

$account = $this->post->account;

$password = $this->post->password;

$encoding = strtoupper($this->post->encoding);

$path

= $this->post->path;

if($encoding != 'UTF8' and $encoding != 'UTF-8') $path = helper::convertEncoding($path, 'utf-8', $encoding);

if($scm == 'Subversion')

{

$versionCommand = "$client --version --quiet 2>&1";

exec($versionCommand, $versionOutput, $versionResult);

if($versionResult)

{

$message = sprintf($this->lang->repo->error->output, $versionCommand, $versionResult, join("
", $versionOutput));

dao::$errors['client'] = $this->lang->repo->error->cmd . "
" . nl2br($message);

return false;

}

$svnVersion = end($versionOutput);

命令执行最终效果截图:

修复建议

目前禅道官方已正式发布修复版本,建议受影响用户尽快升级至安全版本。

如不能升级,可在module/common/model.php文件中的echo $endResponseException->getContent();后面加上exit(); 来修复权限绕过漏洞。

烽火台实验室公众号回复“zentaopoc”可获取漏洞自检脚本,该脚本仅用于检测自有系统是否存在漏洞,若确认漏洞存在请尽快进行版本升级和修复。

原文链接

来源地址:https://blog.csdn.net/C20220511/article/details/129428886

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯