项目管理系统远程命令执行漏洞

渗透技巧 1年前 (2023) admin
447 0 0
项目管理系统远程命令执行漏洞

本文来源自平安银河实验室

作者:李思锐



官方 dockerhub 镜像仓库下载 docker 镜像。

这里下载漏洞相关版本进行diff,开源版本18.0.beta1和18.0.beta2。

运行两个版本,使用beyond compare进行diff,比较两个版本的源代码。
项目管理系统远程命令执行漏洞
根据已公开的漏洞描述是鉴权绕过和命令注入漏洞。

漏洞分析


下面是从补丁逆推漏洞的过程

鉴权绕过

因为是鉴权绕过,所以猜测是通用类/公共类/入口逻辑有问题,优先diff api/module 下的变动。发现这里存在不同(module/common/model.php#L2455)checkPriv函数,由 echo -> die。
项目管理系统远程命令执行漏洞

看下这个函数的定义,和修改处可能的原因,容易看出这个地方存在较大风险,因为 checkPriv 是 return void 即无返回值,正常来说在调用的时候不会关心其返回值,由checkPriv本身处理掉所有异常。

项目管理系统远程命令执行漏洞

但是这里(module/common/model.php#L2455checkPriv函数,由 echo -> die,容易看出这里的echo会导致checkPriv函数实际不起作用,实际在不满足条件的时候后续代码还是会继续执行,所以才进行修复,由 echo 改为 die。

那么审计这个函数的逻辑,尝试找出触发条件。
/** * Check the user has permission to access this method, if not, locate to the login page or deny page. * * @access public * @return void */ 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->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($this->isOpenMethod($module, $method)) return true;
if(isset($this->app->user)) { $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(); } }

简化为:
{ try { // code } catch(EndResponseException $endResponseException) { echo $endResponseException->getContent(); }}

容易看出触发需要try包裹的代码抛出EndResponseException异常,简单搜索一下,容易发现是framework/helper.class.php的end函数。

项目管理系统远程命令执行漏洞
项目管理系统远程命令执行漏洞
查找一下helper::end()

项目管理系统远程命令执行漏洞

容易得到需要进入这个环节if(!commonModel::hasPriv($module, $method))
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->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($this->isOpenMethod($module, $method)) return true;
if(isset($this->app->user)) { $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(); } }

整理一下checkPriv这里触发需要的条件:
  • $this->app->user 即 user 存在

  • !commonModel::hasPriv($module, $method) 没有权限访问指定模块的指定方法

满足这两个条件时,就能让绕过权限校验。

先看条件一, 需要获取到 $this->app->user ,先看下$this如何初始化的
module/common/model.php的构造函数中,user通过setUser方法完成初始化。

项目管理系统远程命令执行漏洞

项目管理系统远程命令执行漏洞
如果 session 中存在user,那么取$this->session->user的值。
如果是访客($this->app->company->guest) 或者 PHP_SAPI == ‘cli’, 则新构造一个“guest”的user对象,取这个guest user的值。

即需要是下面情况之一:
  • session 中有 user 对象,但是对 user 具体内容没有要求,比如guest

  • 访客模式

  • 命令行模式下调用

后两种情况限制条件较大,那么先尝试第一种,尝试是否有途径可以生成 user,因为checkPriv函数有if($this->isOpenMethod($module, $method)) return true;,则先看是否存在一个open method,可以生成 session user
public function isOpenMethod($module, $method){ if(in_array("$module.$method", $this->config->openMethods)) return true;
if($module == 'block' and $method == 'main' and isset($_GET['hash'])) return true;
if($this->loadModel('user')->isLogon() or ($this->app->company->guest and $this->app->user->account == 'guest')) { if(stripos($method, 'ajax') !== false) return true; if($module == 'block') return true; if($module == 'my' and $method == 'guidechangetheme') return true; if($module == 'misc' and $method == 'downloadclient') return true; if($module == 'misc' and $method == 'changelog') return true; if($module == 'tutorial' and $method == 'start') return true; if($module == 'tutorial' and $method == 'index') return true; if($module == 'tutorial' and $method == 'quit') return true; if($module == 'tutorial' and $method == 'wizard') return true; if($module == 'product' and $method == 'showerrornone') return true; } return false; }

三种情况如下,因为后两者的条件显然不满足,所以重点查找config/zentaopms.php定义里是否有可用方法。

  • “$module.$method” 在 config/zentaopms.php定义里面

项目管理系统远程命令执行漏洞

  • $module == ‘block’ and $method == ‘main’ and isset($_GET[‘hash’])

  • $this->loadModel(‘user’)->isLogon() or ($this->app->company->guest and $this->app->user->account == ‘guest’) 即 登录状态或者访客

发现 $config->openMethods[] = ‘misc.captcha’; 即 module/misc/control.php#L223 captcha方法满足条件能够利用,当 $sessionVar 设置为 ‘user’ 的时候,$this->session->set($sessionVar, $captcha->getPhrase()); $this->session->set(‘user’, $captcha->getPhrase());, user 对象被生成出来。
/** * Show captcha and save to session. * * @param string $sessionVar * @param string $uuid * @access public * @return void */ 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(); }

所以鉴权绕过的流程如下:

访问验证码接口,构造 $sessionVar = ‘user’,进入 misc.captcha 方法生成session user ,然后因为session user存在,绕过checkPriv 逻辑,实现能够调用任意模块方法的目的。

命令注入

因为是命令注入,优先查找 exec 关键字/ 组件功能模块,尝试发现diff内容中是否有相关内容,发现 lib/scm/gitrepo.class.php含有相关内容,这里对$client加了一次校验,容易看出这里存在利用的风险,后面补丁做了一次加固。


项目管理系统远程命令执行漏洞

当 $client 可控的时候,直接带入 exec 函数造成 exec(“{$this->client} config core.quotepath false”); 命令执行。

那么看GitRepo的上级调用:

项目管理系统远程命令执行漏洞
项目管理系统远程命令执行漏洞
项目管理系统远程命令执行漏洞
有两处地方调用了setEngine方法,如上图,

update的要求先存在一个repo,repo存在检查(module/repo/control.php),当不存在的时候会返回创建链接,即存在前置条件。

项目管理系统远程命令执行漏洞
create的逻辑 module/repo/model.php

项目管理系统远程命令执行漏洞
这里需要SCM == ‘Gitlab’ 的时候返回true,才能在缺少其他条件/参数的时候通过校验。

项目管理系统远程命令执行漏洞
同时checkConnection函数这里直接拼接client,造成命令注入,导致在update/create 如果能进入checkConnection函数, 便通过client变量已经能够触发命令执行,无须再通过后面的逻辑来完成命令注入。

项目管理系统远程命令执行漏洞
那么总结如下:

create repo 可以通过SCM == Gitlab,创建repo,返回值 locate 字段中有 repo id。

项目管理系统远程命令执行漏洞
update repo 可以直接通过client参数执行命令,但是要求repo先存在。

项目管理系统远程命令执行漏洞
所以需要两者一起使用,流程如下:
1. post /repo-create.html 其中 SCM=Gitlab 创建代码库, 然后获取到repo id
2. post /repo-edit-${id}.html 其中id 为上一步获得的repo id ,通过client变量注入命令,可以通过报错使得接口返回信息


验证过程


访问 /misc-captcha-user.html 获得一个session user (权限绕过)后续都用返回的这个zentaosid。

GET /misc-captcha-user.html HTTP/1.1

项目管理系统远程命令执行漏洞

创建repo, 如果有repo的话这步可跳过 记录下repo id。

项目管理系统远程命令执行漏洞
编辑repo,在这一步触发命令执行。

项目管理系统远程命令执行漏洞
项目管理系统远程命令执行漏洞

验证成功图片

项目管理系统远程命令执行漏洞
项目管理系统远程命令执行漏洞



银河实验室

项目管理系统远程命令执行漏洞

银河实验室(GalaxyLab)是平安集团信息安全部下一个相对独立的安全实验室,主要从事安全技术研究和安全测试工作。团队内现在覆盖逆向、物联网、Web、Android、iOS、云平台区块链安全等多个安全方向。
官网:http://galaxylab.pingan.com.cn/




 好   顾 


JSP下的白魔法:JspEncounter
利用Cloudflare 零信任进行C2通信及防护
使用GitHub Actions进行红队自动化构建
使用VS-Code结合源码进行内核及内核模块远程调试的方法



点赞、分享,感谢你的阅读▼ 


▼ 点击阅读原文,进入官网

原文始发于微信公众号(平安集团安全应急响应中心):项目管理系统远程命令执行漏洞

版权声明:admin 发表于 2023年3月24日 下午6:00。
转载请注明:项目管理系统远程命令执行漏洞 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...