CVSS 10.0 · 未认证 · 已被积极利用(CISA KEV)
GitLab Workhorse(Go 反向代理)与 Puma/Grape(Ruby)之间的解析器差异,使未认证攻击者能够绕过 Workhorse 的加速上传交接,并以攻击者可控的 file.path 访问三个上传端点,从而在 GitLab 主机上实现任意文件读取。
将 files 中的一个字符编码为 %66iles,会使 Workhorse 的上传路由无法匹配(它基于编码后的路径进行匹配),而 Rails 会将其解码回来并命中真正的处理器(它基于解码后的路径进行路由)。该处理器信任 Workhorse 本应覆盖的原始 file.path 参数——因此 file.path=/etc/passwd 会在无凭据的情况下从磁盘被读取。
POST /api/v4/projects/1/repository/%66iles/x?file=&file.path=/etc/passwd&file.size=1
^^^^^^ Workhorse 未匹配 -> 原始 file.path 存活至 Rails
| 版本 | |
|---|---|
| 受影响 | CE/EE 18.7 → 19.1.8、19.2 → 19.2.6、19.3 → 19.3.2 |
| 已修复 | 19.1.8 / 19.2.6 / 19.3.2(2026-09-10) |
不熟悉 GitLab 内部机制?以下是每一层的作用——按请求经过它们的顺序排列。包含关键概念的更深入术语表见
docs/ANALYSIS.md §0。
Internet
│
▼
┌────────┐ ┌───────────┐ ┌────────┐ ┌──────────────────────┐
│ NGINX │────▶│ Workhorse │────▶│ Puma │────▶│ Grape / Rails app │
│(proxy) │ │ (Go) │ │ (Ruby) │ │ (Ruby) │
└────────┘ └───────────┘ └────────┘ └──────────────────────┘
该漏洞存在于 Workhorse(基于编码后路径匹配路由)与 Grape/Puma(基于解码后路径路由)之间的间隙中。见下文。
r.URL.EscapedPath()(编码后)匹配路由;Puma/Grape 基于解码后的路径路由。对 Workhorse 而言 %66iles ≠ 正则 files,但对 Rails 而言会解码为 files。file.path 重写为已签名的临时路径,也从不设置 Gitlab-Workhorse-Multipart-Fields 头——但它仍然代理该请求(带有有效的 Gitlab-Workhorse-Api-Request JWT)。authenticate!,且处理器直接读取 params['file.path'](攻击者的字符串),而非 Workhorse 已验证、路径受限的 params[:file] UploadedFile。Rack::Utils.parse_nested_query 错误字符串("invalid %-encoding (<file bytes>)")泄露——响应会反射文件字节,直到第一个无效的 。补丁添加了 authenticate!,改用已验证的 UploadedFile,并停止反射 e.message。见 docs/ANALYSIS.md §5。
# 1. 启动漏洞实验环境(详情见 lab/README.md)
cd lab && docker compose up -d # 等待约 5 分钟让 GitLab 变为健康状态
# 2. 非破坏性检测
../poc/detect.sh http://localhost:8929
# 3. 针对你已获授权在自有实验环境中读取的文件进行文件读取 PoC
../poc/exploit.sh http://localhost:8929 /var/opt/gitlab/gitlab-rails/etc/gitlab.yml
本内容发布用于防御和教育目的:理解、检测和修补一个已披露、已修补且列入 CISA KEV 列表的 CVE。此处的脚本为单目标,并要求你显式传入目标。
localhost 上运行。请勿将其指向第三方主机。如果你运行 GitLab,请升级到已修复版本——这是唯一真正的补救措施。
gitlab-org/gitlab @ v19.3.1-ee 对比 v19.3.2-eeMIT — 仅限分析和 PoC 代码。GitLab 是 GitLab Inc. 的商标;本仓库与 GitLab Inc. 无关联,亦未获其认可。
| 路径 | 内容 |
|---|
docs/ANALYSIS.md | 完整根因分析——解析器差异、两个 Workhorse JWT、原始 file.path 信任缺陷、补丁 diff,以及反射错误外泄通道。分析基于真实源码(v19.3.1-ee 对比 v19.3.2-ee)。 |
lab/ | 基于 Docker 的漏洞实验环境(gitlab-ce:19.3.1-ce.0)+ 启动说明。 |
poc/ | detect.sh(非破坏性存在性探测)和 exploit.sh(反射错误文件读取)。单目标,需授权。 |
| 组件 | 说明 |
|---|
| NGINX | 最外层反向代理。终止 TLS、提供静态文件、将其余所有内容向内转发。与本漏洞无直接关系。 |
| Workhorse | GitLab 专用的 Go 反向代理。其主要工作是卸载 Ruby 处理较慢的工作——尤其是流式传输大文件上传。对于上传端点,Workhorse 将请求体缓冲到临时文件,签署 JWT,并重写 file.path,使 Ruby 永远不会看到原始上传字节。它还会在每一个转发的请求上打上每请求的 Gitlab-Workhorse-Api-Request JWT(证明“这来自代理”,而非“该用户已认证”)。 |
| Puma | 运行 Rails 应用的 Ruby 应用服务器。接收来自 Workhorse 的请求,运行中间件(Rack),并分发到路由器。 |
| Rack | Ruby Web 服务器接口层。Rack 中间件处理查询字符串解析、会话管理,以及——在此至关重要——验证 Workhorse 的上传 JWT 并构建 UploadedFile 对象。Rack::Utils.parse_nested_query 正是在本漏洞利用中泄露文件内容的函数。 |
| Grape | GitLab 用于所有 /api/v4/* 端点的 REST API 框架。提供路由定义以及诸如 require_gitlab_workhorse!(代理检查)和 authenticate!(用户身份检查)之类的 before 过滤器。运行在 Rails 内、Puma 上、Workhorse 之后——因此它看到的是解码后的 URL 路径。 |
| Rails | 整体 Web 框架(Ruby on Rails)。GitLab 是一个 Rails 单体——模型、服务和中间件都在 Puma 内的这里运行。 |
%