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

这篇具有很好参考价值的文章主要介绍了禅道系统权限绕过与命令执行漏洞分析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

漏洞概述

禅道是第一款国产的开源项目管理软件,也是国内最流行的项目管理软件。该系统在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

<code><span style="padding:0px; max-width:1000%;"> <span style="padding:0px; max-width:1000%;"><span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">public</span> function <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">checkPriv</span><span style="padding:0px; max-width:1000%;">()</span></span></span></code><code><span style="padding:0px; max-width:1000%;">{</span></code><code><span style="padding:0px; max-width:1000%;"> <br> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">try</span></span></code><code><span style="padding:0px; max-width:1000%;"> <br> {</span></code><code><span style="padding:0px; max-width:1000%;"> <br> $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">module</span> = $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->app->getModuleName();</span></code><code><span style="padding:0px; max-width:1000%;"> <br> $method = $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->app->getMethodName();</span></code><code><span style="padding:0px; max-width:1000%;"> <br> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">if</span>($<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->app->isFlow)</span></code><code><span style="padding:0px; max-width:1000%;"> <br> {</span></code><code><span style="padding:0px; max-width:1000%;"> <br> $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">module</span> = $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->app->rawModule;</span></code><code><span style="padding:0px; max-width:1000%;"> <br> $method = $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->app->rawMethod;</span></code><code><span style="padding:0px; max-width:1000%;"> <br> }</span></code><code><span style="padding:0px; max-width:1000%;"><br></span></code><code><span style="padding:0px; max-width:1000%;"> $beforeValidMethods = <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">array</span>(</span></code><code><span style="padding:0px; max-width:1000%;"> <br> <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'user'</span> => <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">array</span>(<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'deny'</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'logout'</span>),</span></code><code><span style="padding:0px; max-width:1000%;"> <br> <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'my'</span> => <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">array</span>(<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'changepassword'</span>),</span></code><code><span style="padding:0px; max-width:1000%;"> <br> <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'message'</span> => <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">array</span>(<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'ajaxgetmessage'</span>),</span></code><code><span style="padding:0px; max-width:1000%;"> <br> );</span></code><code><span style="padding:0px; max-width:1000%;"> <br> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">if</span>(!empty($<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->app->user->modifyPassword) <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">and</span> (!isset($beforeValidMethods[$<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">module</span>]) <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">or</span> !in_array($method, $beforeValidMethods[$<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">module</span>]))) <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">return</span> print(js::locate(helper::createLink(<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'my'</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'changepassword'</span>)));</span></code><code><span style="padding:0px; max-width:1000%;"> <br> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">if</span>($<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->isOpenMethod($<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">module</span>, $method)) <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">return</span> <span style="padding:0px; max-width:1000%; color:rgb(14, 156, 229);">true</span>;</span></code><code><span style="padding:0px; max-width:1000%;"> <br> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">if</span>(!$<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->loadModel(<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'user'</span>)->isLogon() <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">and</span> $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->server->php_auth_user) $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->user->identifyByPhpAuth();</span></code><code><span style="padding:0px; max-width:1000%;"> <br> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">if</span>(!$<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->loadModel(<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'user'</span>)->isLogon() <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">and</span> $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->cookie->za) $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->user->identifyByCookie();</span></code><code><span style="padding:0px; max-width:1000%;"><br></span></code><code><span style="padding:0px; max-width:1000%;"> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">if</span>(isset($<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->app->user))</span></code><code><span style="padding:0px; max-width:1000%;"> <br> {</span></code><code><span style="padding:0px; max-width:1000%;"> <br> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">if</span>(in_array($<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">module</span>, $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->config->programPriv->waterfall) <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">and</span> $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->app->tab == <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'project'</span> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">and</span> $method != <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'browse'</span>) <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">return</span> <span style="padding:0px; max-width:1000%; color:rgb(14, 156, 229);">true</span>;</span></code><code><span style="padding:0px; max-width:1000%;"><br></span></code><code><span style="padding:0px; max-width:1000%;"> $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->app->user = $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->session->user;</span></code><code><span style="padding:0px; max-width:1000%;"> <br> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">if</span>(!commonModel::hasPriv($<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">module</span>, $method))</span></code><code><span style="padding:0px; max-width:1000%;"> <br> {</span></code><code><span style="padding:0px; max-width:1000%;"> <br> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">if</span>($<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">module</span> == <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'story'</span> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">and</span> !empty($<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->app->params[<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'storyType'</span>]) <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">and</span> strpos(<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">",story,requirement,"</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">",{$this->app->params['storyType']},"</span>) !== <span style="padding:0px; max-width:1000%; color:rgb(14, 156, 229);">false</span>) $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">module</span> = $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->app->params[<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'storyType'</span>];</span></code><code><span style="padding:0px; max-width:1000%;"> <br> $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->deny($<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">module</span>, $method);</span></code><code><span style="padding:0px; max-width:1000%;"> <br> }</span></code><code><span style="padding:0px; max-width:1000%;"> <br> }</span></code><code><span style="padding:0px; max-width:1000%;"> <br> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">else</span></span></code><code><span style="padding:0px; max-width:1000%;"> <br> {</span></code><code><span style="padding:0px; max-width:1000%;"> <br> $uri = $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->app->getURI(<span style="padding:0px; max-width:1000%; color:rgb(14, 156, 229);">true</span>);</span></code><code><span style="padding:0px; max-width:1000%;"> <br> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">if</span>($<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">module</span> == <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'message'</span> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">and</span> $method == <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'ajaxgetmessage'</span>)</span></code><code><span style="padding:0px; max-width:1000%;"> <br> {</span></code><code><span style="padding:0px; max-width:1000%;"> <br> $uri = helper::createLink(<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'my'</span>);</span></code><code><span style="padding:0px; max-width:1000%;"> <br> }</span></code><code><span style="padding:0px; max-width:1000%;"> <br> elseif(helper::isAjaxRequest())</span></code><code><span style="padding:0px; max-width:1000%;"> <br> {</span></code><code><span style="padding:0px; max-width:1000%;"> <br> die(json_encode(<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">array</span>(<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'result'</span> => <span style="padding:0px; max-width:1000%; color:rgb(14, 156, 229);">false</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'message'</span> => $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->lang->error->loginTimeout))); <span style="padding:0px; max-width:1000%; color:rgb(175, 175, 175); font-style:italic;">// Fix bug #14478.</span></span></code><code><span style="padding:0px; max-width:1000%;"> }</span></code><code><span style="padding:0px; max-width:1000%;"><br></span></code><code><span style="padding:0px; max-width:1000%;"> $referer = helper::safe64Encode($uri);</span></code><code><span style="padding:0px; max-width:1000%;"> <br> die(js::locate(helper::createLink(<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'user'</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'login'</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">"referer=$referer"</span>)));</span></code><code><span style="padding:0px; max-width:1000%;"> <br> }</span></code><code><span style="padding:0px; max-width:1000%;"> <br> }</span></code><code><span style="padding:0px; max-width:1000%;"> <br> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">catch</span>(EndResponseException $endResponseException)</span></code><code><span style="padding:0px; max-width:1000%;"> <br> {</span></code><code><span style="padding:0px; max-width:1000%;"> <br> echo $endResponseException->getContent();</span></code><code><span style="padding:0px; max-width:1000%;"> <br> }</span></code><code><span style="padding:0px; max-width:1000%;"> <br> }</span></code>

其中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

<code><span style="padding:0px; max-width:1000%;"><span style="padding:0px; max-width:1000%;"><span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">public</span> function <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">captcha</span><span style="padding:0px; max-width:1000%;">($sessionVar = <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'captcha'</span>, $uuid = <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'')</span></span></span></span></code><code><span style="padding:0px; max-width:1000%;"> <br> {</span></code><code><span style="padding:0px; max-width:1000%;"> <br> $obLevel = ob_get_level();</span></code><code><span style="padding:0px; max-width:1000%;"> <br> for($i = 0; $i < $obLevel; $i++) ob_end_clean();</span></code><code><span style="padding:0px; max-width:1000%;"><br></span></code><code><span style="padding:0px; max-width:1000%;"><span style="padding:0px; max-width:1000%;"> header('</span>Content-Type: image/jpeg<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">');</span></span></code><code><span style="padding:0px; max-width:1000%;"><span style="padding:0px; max-width:1000%;"> <br> $captcha = $this->app->loadClass('</span>captcha<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">');</span></span></code><code><span style="padding:0px; max-width:1000%;"> <br> $this->session->set($sessionVar, $captcha->getPhrase());</span></code><code><span style="padding:0px; max-width:1000%;"> <br> $captcha->build()->output();</span></code><code><span style="padding:0px; max-width:1000%;"> <br> }</span></code>

通过上述思路可以成功实现权限绕过,不过经过实际测试发现,能绕过访问的皆为公共模块。因为在禅道的功能权限验证中还有一部分是验证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判断。

禅道漏洞,php,安全,web安全,Powered by 金山文档

具体复现思路如下:

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

1

<code><span style="padding:0px; max-width:1000%;"><span style="padding:0px; max-width:1000%;"><span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">public</span> function <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">create</span><span style="padding:0px; max-width:1000%;">()</span></span></span></code><code><span style="padding:0px; max-width:1000%;">{</span></code><code><span style="padding:0px; max-width:1000%;"> <br> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">if</span>(!$<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->checkClient()) <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">return</span> <span style="padding:0px; max-width:1000%; color:rgb(14, 156, 229);">false</span>;</span></code><code><span style="padding:0px; max-width:1000%;"> <br> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">if</span>(!$<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->checkConnection()) <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">return</span> <span style="padding:0px; max-width:1000%; color:rgb(14, 156, 229);">false</span>;</span></code><code><span style="padding:0px; max-width:1000%;"><br></span></code><code><span style="padding:0px; max-width:1000%;"> $isPipelineServer = in_array(strtolower($<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->post->SCM), $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->config->repo->gitServiceList) ? <span style="padding:0px; max-width:1000%; color:rgb(14, 156, 229);">true</span> : <span style="padding:0px; max-width:1000%; color:rgb(14, 156, 229);">false</span>;</span></code><code><span style="padding:0px; max-width:1000%;"><br></span></code><code><span style="padding:0px; max-width:1000%;"> $data = fixer::input(<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'post'</span>)</span></code><code><span style="padding:0px; max-width:1000%;"> <br> ->setIf($isPipelineServer, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'password'</span>, $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->post->serviceToken)</span></code><code><span style="padding:0px; max-width:1000%;"> <br> ->setIf($<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->post->SCM == <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'Gitlab'</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'path'</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'')</span></span></code><code><span style="padding:0px; max-width:1000%;"><span style="padding:0px; max-width:1000%;"> <br> ->setIf($this->post->SCM == '</span>Gitlab<span style="padding:0px; max-width:1000%; color:rgb(14, 156, 229);">'</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'client'</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'')</span></span></code><code><span style="padding:0px; max-width:1000%;"><span style="padding:0px; max-width:1000%;"> <br> ->setIf($this->post->SCM == '</span>Gitlab<span style="padding:0px; max-width:1000%; color:rgb(14, 156, 229);">'</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'extra'</span>, $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->post->serviceProject)</span></code><code><span style="padding:0px; max-width:1000%;"> <br> ->setIf($isPipelineServer, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'prefix'</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'')</span></span></code><code><span style="padding:0px; max-width:1000%;"><span style="padding:0px; max-width:1000%;"> <br> ->setIf($this->post->SCM == '</span>Git<span style="padding:0px; max-width:1000%; color:rgb(14, 156, 229);">'</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'account'</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'')</span></span></code><code><span style="padding:0px; max-width:1000%;"><span style="padding:0px; max-width:1000%;"> <br> ->setIf($this->post->SCM == '</span>Git<span style="padding:0px; max-width:1000%; color:rgb(14, 156, 229);">'</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'password'</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'')</span></span></code><code><span style="padding:0px; max-width:1000%;"><span style="padding:0px; max-width:1000%;"> <br> ->skipSpecial('</span>path,client,account,password<span style="padding:0px; max-width:1000%; color:rgb(14, 156, 229);">'</span>)</span></code><code><span style="padding:0px; max-width:1000%;"> <br> ->setDefault(<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'product'</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'')</span></span></code><code><span style="padding:0px; max-width:1000%;"><span style="padding:0px; max-width:1000%;"> <br> ->join('</span>product<span style="padding:0px; max-width:1000%; color:rgb(14, 156, 229);">'</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">','</span>)</span></code><code><span style="padding:0px; max-width:1000%;"> <br> ->setDefault(<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'projects'</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'')->join('</span>projects<span style="padding:0px; max-width:1000%; color:rgb(14, 156, 229);">'</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">','</span>)</span></code><code><span style="padding:0px; max-width:1000%;"> <br> ->get();</span></code><code><span style="padding:0px; max-width:1000%;"> <br> $data->acl = empty($data->acl) ? <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'' : json_encode($data->acl);</span></span></code><code><span style="padding:0px; max-width:1000%;"><span style="padding:0px; max-width:1000%;"> <br> if($data->SCM == '</span>Subversion<span style="padding:0px; max-width:1000%; color:rgb(14, 156, 229);">'</span>)</span></code><code><span style="padding:0px; max-width:1000%;"> <br> {</span></code><code><span style="padding:0px; max-width:1000%;"> <br> $scm = $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->app->loadClass(<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'scm'</span>);</span></code><code><span style="padding:0px; max-width:1000%;"> <br> $scm->setEngine($data);</span></code><code><span style="padding:0px; max-width:1000%;"> <br> $info <br> = $scm->info(<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'');</span></span></code><code><span style="padding:0px; max-width:1000%;"> <br> $infoRoot = urldecode($info->root);</span></code><code><span style="padding:0px; max-width:1000%;"><span style="padding:0px; max-width:1000%;"> <br> $data->prefix = empty($infoRoot) ? '</span><span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">' : trim(str_ireplace($infoRoot, '</span><span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">', str_replace('</span>\\<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">', '</span>/<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">', $data->path)), '</span>/<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">');</span></span></code><code><span style="padding:0px; max-width:1000%;"><span style="padding:0px; max-width:1000%;"> <br> if($data->prefix) $data->prefix = '</span>/<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">' . $data->prefix;</span></span></code><code><span style="padding:0px; max-width:1000%;"> <br> }</span></code>

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

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

1

<code><span style="padding:0px; max-width:1000%;"><span style="padding:0px; max-width:1000%;"><span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">public</span> function <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">edit</span><span style="padding:0px; max-width:1000%;">($repoID, $objectID = <span style="padding:0px; max-width:1000%; color:rgb(14, 156, 229);">0</span>)</span></span></span></code><code><span style="padding:0px; max-width:1000%;">{</span></code><code><span style="padding:0px; max-width:1000%;"> <br> $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->commonAction($repoID, $objectID);</span></code><code><span style="padding:0px; max-width:1000%;"><br></span></code><code><span style="padding:0px; max-width:1000%;"> $repo = $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->repo->getRepoByID($repoID);</span></code><code><span style="padding:0px; max-width:1000%;"> <br> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">if</span>($_POST)</span></code><code><span style="padding:0px; max-width:1000%;"> <br> {</span></code><code><span style="padding:0px; max-width:1000%;"> <br> $noNeedSync = $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->repo->update($repoID);</span></code><code><span style="padding:0px; max-width:1000%;"> <br> <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">if</span>(dao::isError()) <span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">return</span> $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->send(<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">array</span>(<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'result'</span> => <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'fail'</span>, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'message'</span> => dao::getError()));</span></code><code><span style="padding:0px; max-width:1000%;"> <br> $newRepo = $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->repo->getRepoByID($repoID);</span></code><code><span style="padding:0px; max-width:1000%;"> <br> $actionID = $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->loadModel(<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'action'</span>)->create(<span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'repo'</span>, $repoID, <span style="padding:0px; max-width:1000%; color:rgb(221, 17, 68);">'edited'</span>);</span></code><code><span style="padding:0px; max-width:1000%;"> <br> $changes = common::createChanges($repo, $newRepo);</span></code><code><span style="padding:0px; max-width:1000%;"> <br> $<span style="padding:0px; max-width:1000%; color:rgb(202, 125, 55);">this</span>->action->logHistory($actionID, $changes);</span></code>

跟踪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')

{

/* Get svn version. */

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

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

if($versionResult)

{

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

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

return false;

}

$svnVersion = end($versionOutput);

命令执行最终效果截图:

禅道漏洞,php,安全,web安全,Powered by 金山文档

修复建议

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

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

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

原文链接文章来源地址https://www.toymoban.com/news/detail-723970.html

到了这里,关于禅道系统权限绕过与命令执行漏洞分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • dvwa命令执行漏洞分析

    high难度的源码: $target = trim($_REQUEST[ ‘ip’ ]);是一个接收id值的变量 array_keys()函数功能是返回包含原数组中所有键名的一个新数组。 str_replace() 函数如下,把字符串 “Hello world!” 中的字符 “world” 替换为 “Shanghai”: shell_exec()函数是执行Linux命令函数,可以获取全部数

    2024年02月07日
    浏览(45)
  • 漏洞深度分析|CVE-2022-1471 SnakeYaml 命令执行漏洞

    YAML是一种数据序列化格式,设计用于人类的可读性和与脚本语言的交互。 SnakeYaml是一个完整的YAML1.1规范Processor,支持UTF-8/UTF-16,支持Java对象的序列化/反序列化,支持所有YAML定义的类型。 https://github.com/snakeyaml/snakeyaml SnakeYaml通常使用方法如下: new Yaml(new Constructor(TestDataC

    2023年04月27日
    浏览(40)
  • [JAVA安全]CVE-2022-33980命令执行漏洞分析

    在 i春秋的漏洞靶标上看见了此漏洞,所以前来分析一下漏洞原理,比较也是去年 7月的漏洞。 漏洞描述:Apache官方发布安全公告,修复了一个存在于Apache Commons Configuration 组件的远程代码执行漏洞,漏洞编号:CVE-2022-33980,漏洞威胁等级:高危。恶意攻击者通过该漏洞,可在

    2024年02月15日
    浏览(43)
  • 安恒信息-明御安全网关 命令执行漏洞分析

    目录 注意 漏洞描述 漏洞位置 漏洞代码 POC: 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。 安恒信息-明御安全网关存在命令执行漏洞,导致攻击者可以直接执行系统命令,从而控制目标系

    2024年02月12日
    浏览(47)
  • CVE-2021-40444分析报告微软MHTML远程命令执行漏洞

    2021 年 8 月 21 日,MSTIC 观察到一名 Mandiant 员工在社交媒体上发布的帖子,该员工具有跟踪 Cobalt Strike Beacon 基础设施的经验。所写文章重点介绍了一个于 2021 年 8 月 19 日上传到 VirusTotal的 Microsoft Word 文档(SHA-256:3bddb2e1a85a9e06b9f9021ad301fdcde33e197225ae1676b8c6d0b416193ecf)。 MSTIC 对样

    2024年02月08日
    浏览(37)
  • 【认证绕过】NACOS身份认证绕过漏洞分析

    前言 工作中遇到一个nacos服务认证绕过的问题,在此总结一下漏洞原因。 官方文档描述: Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。 Nacos 帮助您更敏捷和容易地构建、

    2023年04月13日
    浏览(52)
  • Solr Shiro Log4j2 命令执行--文件读取--反序列化--身份权限绕过--命令执行

    Apache Velocity是一个基于Java的模板引擎,它提供了一个模板语言去引用由Java代码定义的对象。Velocity是Apache基金会旗下的一个开源软件项目,旨在确保Web应用程序在表示层和业务逻辑层之间的隔离(即MVC设计模式)。 Apache Solr 5.0.0版本至8.3.1版本中存在输入验证错误漏洞。攻击

    2024年02月08日
    浏览(44)
  • Couchdb-权限绕过--命令执行--(CVE-2017-12635)&&(CVE-2017-12636)--H2database命令执行--(CVE-2022-23221)

    采用Vulfocus靶场环境进行复现,搭建操作和文章参考具体搭建教程参考vulfocus不能同步的解决方法/vulfocus同步失败。 Apache CouchDB是一个开源数据库,专注于易用性和成为\\\"完全拥抱web的数据库\\\"。它是一个使用JSON作为存储格式,JavaScript作为查询语言,MapReduce和HTTP作为API的NoSQL数

    2024年02月08日
    浏览(32)
  • RCE 远程代码执行漏洞分析

    Remote Command/Code Execute 远程命令执行/远程代码执行漏洞 这种漏洞通常出现在应用程序或操作系统中,攻击者可以通过利用漏洞注入恶意代码,并在受攻击的系统上执行任意命令。 PHP 代码执行 PHP 代码注入 OS 命令执行 OS 命令注入 Java、Python…… Web 应用程序 远程服务 操作系统

    2024年02月08日
    浏览(40)
  • Apache ActiveMQ 远程代码执行漏洞分析

    Apache ActiveMQ官方发布新版本,修复了一个远程代码执行漏洞(CNVD-2023-69477  CVE-2023-46604),攻击者可构造恶意请求通过Apache ActiveMQ的61616端口发送恶意数据导致远程代码执行,从而完全控制Apache ActiveMQ服务器。 影响版本 ‍ 环境搭建 没有找到合适的 docker 镜像 ,尝试自己进行

    2024年02月03日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包