漏洞

Atlassian:图片入口为什么能读到 Crowd 应用密码

Atlassian 八款自托管产品的受影响旧版本允许公开资源入口读取应用目录中的内部文件,部分 Crowd 单点登录部署的应用密码可能因此泄露。处置需要安装厂商完整修补包并更新所有节点,已经外泄的应用凭据还需要单独处理。

浅暖纸手绘:文件柜的公开图片抽屉与内部文档隔间之间,一条红色路径绕过隔板,外层柜体仍保持完整。
文章导航

Jira 的登录页也要加载图片、脚本和样式。这些资源必须能在登录前访问。10 月 5 日,Atlassian 发布紧急安全公告,要求八款自托管产品升级,原因正出在这条常被忽略的公开路径上。攻击者借图片资源入口提交带目录片段的名字,服务器最终可能返回应用内部的配置文件。

其中一份文件让影响变得具体。按官方文档启用 Crowd 单点登录的 Jira,会把应用名、应用密码和 Crowd 地址写进 WEB-INF/classes/crowd.properties。Crowd 负责集中管理用户与认证,Jira 平时凭这些信息向它发起请求。文件落到外人手里后,下一步就取决于这份应用身份在 Crowd 中获准做什么,以及 Crowd 是否接受来自攻击者所在位置的连接。

处理 CVE-2026-21589,需要把公开入口的修复与身份凭据的处置分开完成。产品更新不会自动使已经泄露的 Crowd 应用密码失效,更换这项凭据仍需单独安排。10 月 7 日,SANS 报告其蜜罐自前一天起收到利用尝试,使用的正是公开研究中的资源路径。对仍暴露在公网的旧实例,等待一份确定的入侵通知会把处置拖得太晚。

仍在使用受影响自托管版本的团队,应先限制实例的外部网络访问,再安装厂商的完整产品更新。后文保留八款产品的分支修复表,方便按现有安装核对。Cloud 产品由 Atlassian 处理,使用云服务的客户无需部署这些本地补丁。

1 两个冒号,在程序内部变成了斜杠

先把“应用目录”说清楚。Tomcat 中的一个 Web 应用有自己的资源根目录。浏览器可以直接请求里面的公开图片,却不能直接下载 WEB-INF 下的部署描述、类文件和部分配置。服务器端代码仍需要读取这些内部文件,否则应用无法运行。这次漏洞利用的正是服务器端读取,攻击者必须知道目标文件的准确名字和路径,无法借它浏览或列出目录。

Atlassian 的插件资源系统负责把资源名映射到真实内容。官方 6.0.7 源码包中的 com/atlassian/plugin/webresource/impl/http/Router.java 第 40–67 行注册了资源与源码路由。处理器收到资源名后,会调用 unescapeSlashes(),再交给 Controller。这个函数把 :: 换回 /,与生成 URL 时的反向转换配套。

// Router.java,资源名进入后续查找前的转换
public static String unescapeSlashes(String string) {
    return string.replaceAll("::", "/");
}

这次转换改变了后面程序眼中的路径。请求中的双冒号原本只是普通字符,转换后成为目录分隔符。带在它旁边的两个点也随之具有“回到上级目录”的意义。前面的 Web 服务器处理请求时还没有经过这次转换,Java 资源查找收到的名字却已经带上了 ../。这个新路径需要在实际读取前受到限制。

斜杠转换还需要接上一项资源配置,才会走到文件。在研究者公开的 Jira 示例中,颜色选择器把 images/ 映射到应用中的一个图片目录,并标记 source=webContextStatic。结尾的斜杠允许后面继续带文件名。这样,公开请求就获得了一个真实目录作为起点。watchTowr 在 Jira、Confluence 与 Bitbucket 上验证了这类读取,具体资源入口随产品而变。

2 查找资源的兼容分支,把剩余路径接了回去

转换后的名字进入 ResourceServingHelpers.getResource(),它首先按完整资源名查找。找不到时,就进入 getResourceRelativeToWebResource()。在同一源码包的 impl/helpers/ResourceServingHelpers.java 第 569–597 行,这个兼容函数从名字末尾逐段拆下内容,暂存为 filePath,直到剩下的前缀命中已经注册的资源。例如,注册项是 images/,请求可以继续指定它下面的图片。

拆下来的内容没有消失。splitLastPathPart() 的第 646–656 行只寻找最后一个斜杠、分割字符串,没有删除上级目录片段。循环结束时,函数把累积的 filePath 交给 ResourceFactory.createResourceWithRelativePath(),其中保留的 ../ 也一并传了下去。

impl/snapshot/resource/ResourceFactory.java 第 72–95 行把这个相对路径交给路径策略。随后,impl/snapshot/resource/strategy/path/RelativePathStrategyDecorator.java 第 13–20 行的 getPath() 把它与原始资源位置拼接。拆分时保留下来的上级目录片段,也就进入了最终路径。

资源配置中的 webContextStatic 决定了读取方式。impl/snapshot/resource/strategy/stream/StreamStrategyFactory.java 第 21–27 行据此选择 Tomcat 策略,同目录下 TomcatStreamStrategy.java 第 15–18 行再通过 ServletContext.getResourceAsStream() 取流。读取仍在 Web 应用上下文内,里面的 WEB-INF 也属于可寻址的应用资源。浏览器直接下载这个目录所受的 HTTP 限制,没有阻止这里的服务器端调用。

下面的离线字符串模型保留了这段拆分循环,没有运行 Atlassian 产品或发起网络请求。正常文件名与包含上级目录的名字都能命中 images/,后者的上级目录片段会完整留在相对路径中。

注册资源前缀       images/
正常请求           images/palette.png
留下的相对路径     palette.png

说明性输入         images/../internal.txt
留下的相对路径     ../internal.txt

实际可读文件由产品的资源注册与应用上下文决定。watchTowr 的产品演示取得了应用内的部署或重写配置,读取停在应用目录范围内。管理员应据此盘点这片目录中的敏感内容。

公开资源名中的双冒号经 Router 还原为斜杠,相对路径继续拼接,应用目录内的内部文档可能被返回。
图 1:同一个名字从请求进入资源读取,分隔符的含义发生变化。文件名用于说明关系,实际入口及可读对象由产品配置决定。查看大图

3 配置文件里的密码,代表的是一个应用身份

读到 web.xml 可以证明内部文件暴露,后果还取决于同一范围内的其他内容。Atlassian 的 Jira 集成文档把用户目录连接与可选的 Crowd 单点登录分成两步。启用后者时,才要求复制 crowd.properties 到 Jira 的 WEB-INF/classes。采用这项单点登录配置的部署会保存该文件,其位置也已经写在官方文档里。文件中的 application.name 与 application.password 对应 Crowd 中登记的应用。

应用密码与普通用户密码承担不同工作。它让 Crowd 认出“这是 Jira 在请求用户目录”,随后再按这项应用的目录映射与权限处理操作。因此,读到密码后的问题变成了两件事。连接能否到达 Crowd,应用身份又能否执行想要的目录动作。

Crowd 可以限制某项应用从哪些地址连接。官方集成步骤中的 Remote Addresses要求登记 Jira 所在主机的地址。严格执行这项限制时,未获准来源的连接会被拒绝,持有密码也无法从那里使用 Jira 的应用身份。限制设得过宽、或请求已经来自获准的位置时,这一道检查的效果便不同。

连接被接受后,权限仍然重要。Crowd 的目录权限文档区分目录级和应用级授权,后者可以分别允许添加、修改或删除用户和组,目录级禁用会覆盖应用级允许。研究者演示的环境允许用外泄应用身份创建用户,并把用户加入 Jira 管理组。这个结果需要相应的可达性和写权限,不能自动套到每一个接入 Crowd 的站点。

排查时先找到这份配置对应的 Crowd 应用,再核对它的目录映射、写权限和允许来源。发现可用凭据暴露,就需要同时处理文件读取入口与这项应用身份。

4 更新各服务节点,修补期间维持访问限制

10 月 5 日官方公告覆盖 Bitbucket、Confluence、Jira Software、Jira Service Management、Bamboo、Crowd、Crucible 与 Fisheye。Cloud 已由厂商处理,公告要求自托管安装更新。下表给出各维护线的修复起点,安装目标应选择自己仍受支持分支上的这些构建或更新维护版。跨到另一个大版本前,还须核对其发行说明与兼容要求,不能仅按数字大小判断补丁已经包含。

修复起点与已核对的维护包,2026 年 10 月 13 日

产品各分支修复起点当前维护包入口
Bitbucket Data Center9.4.26、10.2.8、10.5.110.2.8 LTS
Confluence Data Center9.2.26、10.2.1910.2.19 LTS
Jira Software Data Center9.12.40、10.3.26、11.3.1211.3.12 LTS
Jira Service Management Data Center5.12.40、10.3.26、11.3.1211.3.12 LTS
Bamboo Data Center10.2.24、12.1.1212.1.12 LTS
Crowd Data Center6.3.7、7.0.3、7.1.7、7.2.47.2.4 LTS
Crucible4.9.154.9.15
Fisheye4.9.154.9.15

最后一列提供当前维护线的一个明确入口,旧分支已有对应修复时可留在原分支更新,不要求立即跨大版本。例如,Jira 10.3 的部署仍有 10.3.26 可用。应安装完整产品包,单个资源库版本不能替代产品修补。Bitbucket 10.2 的发行说明还写明,从 10.2.0–10.2.2 升到 10.2.3 或之后版本需要整个集群停机并重启,这次升到 10.2.8 也包含这项前提。

若需要在限制外部访问的基础上增加请求过滤,厂商提供了 WAF 正则,以及适用于 Confluence、Jira、JSM、Bamboo、Crowd 的 Tomcat RewriteValve 配置,Bitbucket 使用独立的 urlrewrite.xml 规则。规则识别目录分隔符旁的上级目录片段,也覆盖 URL 编码形式。应沿用公告当前的完整规则,再在自己的代理与应用链路上核对它实际看到的是原始 URI 还是已经解码的路径。普通的用户登录要求无法替代这里的路径限制。

临时限制必须覆盖真实服务节点。Bitbucket 的公告特别包括代码镜像和 mirror farm 节点。更新失败的节点继续隔离,回退只选已含修复的兼容包。应用重新启动后,检查正在处理请求的实际安装目录与构建,再验证正常登录、图片脚本、页面和必要集成能继续工作。移除旧安装目录或部署容器前保留恢复所需备份,避免回滚把易受影响的入口一同带回来。

5 文件泄露后,继续追踪凭据与权限

保存代理与应用访问日志,按官方说明对请求行最多做两次 URL 解码,再检查分隔符旁的上级目录片段,或直接用公告正则搜索原始日志。请求时间、状态和响应大小有助于缩小调查范围,确认敏感内容外传还需要能证明返回内容的记录。只有扫描请求时,结论停在尝试。确认敏感文件被返回后,继续处置其中的活跃凭据。

对 Crowd 集成,核对这项应用身份的调用、用户和组的变更,并更换可能外泄的应用密码。更换时同步更新对应客户端配置,再验证合法连接,避免身份系统与 Jira 各用一份不同的密码。已出现异常账号或组成员变更的环境,还要撤销未授权访问,追踪这段身份权限能接触的系统。新的密码不会自动删除已经建立的账号与权限。