禅道小范围版本通杀代码审计

April 10, 2025 / GowNinng / 88阅读 / 0评论

前段时间朋友打了一个站,发现资产包含禅道15.x版本,朋友说随后发给我了一份分析文章,但是并没有poc,所以着手代码审计一下。

一开始我基本上没看过禅道的源码,首先我看别人文章说的15-18有不同利用链的越权和rce,当前版本是15.0.rc2,rce估计都是在登录后才能操作功能点,所以先复现网上说的权限绕过漏洞。

下载15.0.rc2源码,然后在本地打开,老样子,直接debug调试。首先在这个源码中,有部分方法可以在用户未登录的情况下进行调用,允许未授权调用的方法在isOpenMethod() 函数中,先全局搜索下isOpenMethod() 函数。

首先看到这个函数带有判断,未登录用户能访问xxx接口,登录用户且登录账户不为guest能访问xxx接口。那我们全局搜索下isLogon函数。

这个代码的意思是,返回当前登录session的用户并且用户名不为guest。

那么好,我如果想访问剩下的接口,如何让这个函数成功调用,可以想到session覆盖这个东西,我可以调用一个能够覆盖session的方法,让session有user这个key,这个key也可以为空,只要不等于guest就可以。

由于这个程序session设置的写法为$this->session->set(),所以全局搜索带有$this->session->set并且能够可控变量的方法。

看着有好几个,找了这么多,看到了captcha方法可以控制session的key。

那我们看看怎么引用,直接构造url。

禅道的get路由构造是这样的:

index.php?m=misc&f=captcha&sessionVar=user

对应以下路径。

其实在很多情况下会遇到很多静态路由,禅道设置伪静态后会启用静态路由,静态路由的构造和get的不一样,去掉了key并且用横杠连接。

例如: index.php/misc-captcha-user.json

后缀为json的会输出json格式,后缀为html的话会输出html格式。

路由格式在程序中的助手函数中的createLink方法写的很清晰。

访问路由,会出现验证码,有的可能不会,但是不影响覆盖session。

接着我们访问那些需要登录的接口,就访问tutorial模块的wizard方法,这个模块是禅道的新手教程模块,正常来说访问这个接口会跳转到登录页。但是现在访问会空白,说明成功覆盖session。

那好既然成功绕过权限了,但是我并不想着急找rce漏洞,我想看看能不能访问用户列表,看用户列表中能不能查看用户密码。

用户模块里可能会泄露一些用户帐密的信息。todo方法允许将用户查出来,然后直接将对象渲染到页面,如果直接访问该方法,会让重新登陆。

todo方法最后以$this->display();输出到浏览器上,进一步看下这个方法。

继续看parse方法。

如果viewType == 'json',则渲染json格式。不是的话默认渲染方法,适用于viewType = html的时候。

getViewType这个方法的主要功能是确定当前请求应该使用哪种视图类型,这个就不带单独分析了。

禅道的新手教程模块路由允许fetch一部分模块的方法,就是当跳板,那么我们可以通过这个wizard调到todo方法,从而实现权限绕过访问用户数据。那我们构造可访问的url:index.php?m=tutorial&f=wizard&module=user&method=todo&params=dXNlcklEPTI=&t=json 其中dXNlcklEPTI=为base64加密,因为todo方法中params传入的是一个base64字符串,解密字符串为userID=2,直接在浏览器中访问。

成功输出MD5password,这个password可以直接登录,抓包替换就行,原因是因为先前段MD5加密密码然后再请求后端,后端检测到密码长度为32位,直接和数据库进行对比登录。
成功获取到密码,那么就能直接登录后台了。

问题来了,我看网上公开的poc都不能用,疑似版本太低缺少高版本可利用的功能漏洞点,看网上有说文件包含的,简单看了下没找到直接利用的poc,主要是也想自己审计审计。全局搜索php相关文件包含函数。

经过漫长的寻找,找到了一个可控变量的方法,里面有include方法。

是一个助手函数,helper::import()看看有没有被引用。

也是经过漫长的寻找,找到了api下的getMethod方法,参数可控,继续看看有没有被引用。

找到了,api下的dedug方法,传入的是base64加密的文件,先看代码。

传入$filePath和extendControl跳到getMethod方法,进入此方法。

先获取$filePath的文件夹,如果传入的是xx/xxx/xxxx/1.php,则输出xx/xxx/xxxx,先debug一下看看是不是这样。debug方法打断点,浏览器访问debug方法接口。

用之前获取的密码登录,然后访问url http://localhost/www/index.php?m=api&f=debug&filePath=RDpccGhwc3R1ZHlfcHJvXFdXV1xtb2R1bGVccGhwaW5mby5waHA=&action=extendControl 其中RDpccGhwc3R1ZHlfcHJvXFdXV1xtb2R1bGVccGhwaW5mby5waHA=为D:\phpstudy_pro\WWW\module\phpinfo.php

先base64解码,然后因为action=extendControl进入getMethod方法。

$fileName先获取当前文件的文件夹,$className获取当前文件的文件夹的上一层的名称,即为www,下一句代码是一个条件判断,用于检查指定的类www是否存在,如果不存在则尝试进入helper::import方法。

进入到此方法,$file为文件夹绝对路径,但是传入的是文件夹不是文件,导致下一步返回false,退出程序,导致不能文件包含,那怎么办?

将之前的D:\phpstudy_pro\WWW\module\phpinfo.php改成D:\phpstudy_pro\WWW\module\phpinfo.php\123,那么getMethod中$fileName会读取成D:\phpstudy_pro\WWW\module\phpinfo.php,传入到helper::import的$file就是D:\phpstudy_pro\WWW\module\phpinfo.php,phpinfo.php会被判断成文件,然后进行文件包含。

现在$filename变成了D:\phpstudy_pro\WWW\module\phpinfo.php

直接包含成功。那么就该找上传点了,上传任意文件,将命令写入文件上传。

先看头像上传功能。

上传带有php代码的图片。

上传完剪切直接点x关闭,要不然会导致php代码删减。

f12找到该图片路径,但是不是绝对路径,我们再看看程序中有没有泄露绝对路径的功能点。

发现后台中的这两个功能点存在绝对路径泄露。直接拼接刚刚找到的图片路径。

D:/phpstudy_pro/WWW/www/data/upload/1/202504/09181014014632h8/任意名称

可以看到已经出现phpinfo的内容了,放到浏览器再访问下。

http://localhost/www/index.php?m=api&f=debug&filePath=RDovcGhwc3R1ZHlfcHJvL1dXVy93d3cvZGF0YS91cGxvYWQvMS8yMDI1MDQvMDkxODEwMTQwMTQ2MzJoOC8xMQ==&action=extendModel

成功RCE,后续抽空再看看其他功能点和和高版本。


评论