文章总结: 本文分析GitLabCVE-2026-85706未授权任意文件读取漏洞,CVSS10.0满分。漏洞由路由歧义、JWT注入、认证顺序错误及File.read无路径约束四缺陷叠加导致,可读取服务器任意文件。利用存在回显截断、大小上限等限制。建议升级至19.3.2等修复版本,轮换泄露凭据,并排查日志特征。 综合评分: 92 文章分类: 漏洞分析,应急响应,安全建设
GitLab CVE-2026-85706 未授权任意文件读取分析:一个末尾斜杠打穿的满分漏洞
原创
Kratos Kratos
Kratos Sec
2026年9月13日 14:10 北京
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
GitLab CVE-2026-85706 未授权任意文件读取分析:一个末尾斜杠打穿的满分漏洞
9 月 10 日 GitLab 发布例行补丁,其中 CVE-2026-85706 拿到了 CVSS 10.0 满分:未授权、单个 HTTP 请求、读取服务器任意文件。watchTowr 的蜜网在补丁发布次日就观测到了探测流量,CISA 已将其列入 KEV 目录。这篇把原理、复现、利用限制、自查和修复讲清楚,重点说一个被多数预警稿忽略的问题:它到底能不能真的”读任意文件”。
0x0 漏洞概述
| 项目 | 内容 |
| — | — |
| CVE 编号 | CVE-2026-85706 |
| 漏洞类型 | 路径遍历 / 未授权任意文件读取 |
| CVSS 评分 | 10.0(CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:N) |
| 影响组件 | Repository Commits API(GitLab CE/EE) |
| 影响版本 | 18.7 ≤ v < 19.1.8;19.2 ≤ v < 19.2.6;19.3 ≤ v < 19.3.2 |
| 修复版本 | 19.1.8 / 19.2.6 / 19.3.2 |
| 报告者 | s3ntago(HackerOne) |
| 在野利用 | 已观测到探测与利用,CISA KEV 收录 |
时间线:9 月 10 日官方发补丁,9 月 11 日 watchTowr 蜜网捕获行为探测、vulhub 提交完整复现环境(pull 800),9 月 12 日起公开复现文章扩散。从补丁到武器化的间隔不到 48 小时。
GitLab 自建实例上沉淀的是源代码、CI/CD 流水线凭据、云密钥、内网拓扑,这类入口一旦失守暴露的不是单个应用,而是整个研发体系。所以它值 10 分。
0x1 原理:四个缺陷叠出来的满分
这个漏洞不是单点失误,是四个环节各自”差一点”叠出来的。单独看每一个都算不上致命,串起来就是未授权任意文件读取。
第一层:末尾斜杠造成路由歧义。 GitLab 的架构里 Workhorse 是前置代理,大文件上传由它一手处理,它内部有一条严格锚定的上传路由,匹配 /repository/commits(无末尾斜杠)。攻击者在路径末尾多加一个斜杠变成 /repository/commits/,Workhorse 的锚定匹配失败,认为这不是上传请求,原样转发给后端 Rails;而 Rails 的路由不区分这个斜杠,依然把它分发到 Repository Commits API。同一个 URL,两边的理解不一致,边界就这么被让出来了。
第二层:Workhorse 的内部 JWT 照常注入。 Rails 侧的接口有 require_gitlab_workhorse! 防线,本意是”只接受来自 Workhorse 的请求”。问题在于 Workhorse 转发所有代理请求时都会附带内部 JWT,不管请求是不是它主动处理过的。于是这道防线照常放行,攻击者构造的原始表单参数不经重写直达 Rails。
第三层:认证顺序反了。 目标接口不是没有认证,而是认证做晚了。请求进来后先做项目读授权,然后就去读文件,用户认证在文件读取完成之后才调用。文件内容已经进了内存,认证拦得住后续流程,拦不住已经发生的读取。
第四层:File.read 无路径约束。 服务端直接信任请求侧传入的文件元数据(file.path),据此读取本地文件,没有任何路径限制。这里甚至不需要经典的 ../ 遍历,直接给绝对路径就行。
回显机制是个意外之喜。 就算读完文件,内容怎么带出来?这里靠 Rack 的表单解析报错:整份文件内容被当作表单字符串解析,遇到非法百分号编码(% 后面不是两个十六进制字符)时,Rack 抛出的异常消息会把出错的那段内容写进 HTTP 400 响应体。文件内容就这样从错误信息里漏了出来。
0x2 复现
vulhub 已提供完整环境和 PoC(github.com/vulhub/vulhub/pull/800),基于官方 Docker 镜像 19.3.1 可稳定复现,升到 19.3.2 后同样请求被认证直接阻断。
Step 1:匿名拿一个公开项目 ID
curl -s "http://target/api/v4/projects?visibility=public&simple=true&per_page=100"
利用前提是目标实例上存在至少一个公开项目,这个接口匿名可查。
Step 2:触发文件读取
curl -i -X POST "http://target/api/v4/projects/{id}/repository/commits/" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "file=" \
--data-urlencode "file.path=/var/opt/gitlab/gitlab-rails/etc/gitlab.yml" \
--data-urlencode "file.size=1"
两个细节:URL 末尾的斜杠不能省,省了就进了 Workhorse 的正常上传链路;file.path 直接给绝对路径。
选 gitlab.yml 做默认目标是有讲究的:这份文件由 Omnibus 自动生成,内容里必然含有带 % 的序列,必定触发 Rack 解析报错回显。响应 400,错误信息里能看到文件内容片段。
0x3 冷静一下:它到底能读什么
大量预警稿标题写”任意文件读取”,实际利用是有条件的。把限制说清楚,排查时才不会误判风险面。
文件总是被读,但内容不一定回显。 回显依赖非法百分号编码触发 Rack 报错。内容规整的纯文本(比如 /etc/passwd)被读了,但响应可能只有一个 401,什么都带不出来。能稳定回显的是那些天然含 % 序列的文件:gitlab.yml、redis.conf 这类。
回显是截断的。 Rack 2.2.23 的表单解析按 & 和 ; 切分参数。整份文件被当作表单字符串,错误信息只包含非法 % 所在的那一段。拿 invalid %-encoding BEFORE_AMP&AFTER_AMP 做实验,& 之后的内容直接丢弃。也就是说,凭据如果排在截断点之后,泄露不出来。
大小有上限。 Rack 默认解析上限 4MB(RACK_QUERY_PARSER_BYTESIZE_LIMIT=4194304),超出的文件不走回显。
权限跟着 git 服务账号走。 底层是 git 对目标文件做 stat 和 read,读不到的照样读不到。
但这些限制挡不住它成为 10 分漏洞。一方面 gitlab.yml 这个必中目标恰恰是整台服务器最要命的文件:数据库连接串、Redis 配置、secret_key/otp_key、SMTP、LDAP/SSO、对象存储密钥全在里面。secret_key 拿到就可以伪造 Rails 会话 Cookie 直接接管管理员,Redis 凭据可以接着打内网。另一方面,即便内容回显不出来,400/401/500 的响应差异也足够做存在性探测,攻击者可以借此摸清服务器文件布局、权限边界和运行环境,为下一步铺路。
0x4 自查:判断自己有没有中招
先确认版本。 影响范围是 18.7 ≤ v < 19.1.8、19.2 ≤ v < 19.2.6、19.3 ≤ v < 19.3.2。管理后台 Help 页或 cat /opt/gitlab/version-manifest.txt 可查。GitLab.com 和 GitLab Dedicated 用户无需动作。
再翻日志。 在网关、Workhorse、NGINX 访问日志里找这类特征:
POST /api/v4/projects/{id}/repository/commits/,注意末尾斜杠,这是最硬的指纹;- 请求体里带
file.path参数且值为绝对路径; - 请求头无
Authorization/PRIVATE-TOKEN; - 响应码 400 且响应体里出现本不该出现在 API 报错里的文件内容片段。
WAF 可以加一条临时规则:拦截路径匹配 ^/api/v4/projects/[^/]+/repository/commits/ 的 POST 请求。
重点排查公网实例。 暴露在互联网上的自建 GitLab 风险最高,回溯窗口建议覆盖 9 月 10 日补丁发布之前的时段,漏洞在补丁公开前就存在。
0x5 修复与应急
升级是唯一彻底手段。 19.3.2 / 19.2.6 / 19.1.8 三选一,按当前版本线对号入座。官方修复 commit 为 0d9ce3e758a85f0690be751e213625f7902c0361。注意两点:本批补丁含数据库迁移,单节点实例升级过程会停机,多节点按零停机流程走;19.3.2 带 post-deploy 迁移,升级后留意完成状态。
暂时无法升级的: 把 GitLab Web 与 API 入口收敛到可信网段或 VPN 之后,禁止公网直连;对未登录状态下访问仓库提交类接口的请求启用监控告警。
确认或怀疑已中招的,升级只是第一步。 假设 gitlab.yml 已泄露,按清单轮换:数据库密码、Redis 密码、实例加密密钥(secret_key 等)、对象存储密钥、SMTP 凭据,以及所有集成配置里的第三方 token。同时核查高级搜索、对象存储、镜像库有没有被读取或篡改的痕迹。
0x6 同批补丁里的其他角色
这次补丁日一共修了十几个洞,除了主角之外值得留意的:
- CVE-2026-87719(9.9,EE):GraphQL 订阅序列化器反序列化缺陷,有 Duo Chat 权限的用户可绕过对象访问控制,拿到 Advanced Search 实例配置和敏感凭据。对启用了高级搜索的企业,等于把全实例代码检索能力交给了普通账号。和 85706 组合使用时危害放大。
- CVE-2026-88765(8.5,EE):Unicode 转换缓冲区溢出,导入恶意构造的 Git 项目导出可 RCE,影响版本追溯到 12.3。
- CVE-2026-79708(8.5):Developer 角色可借策略测试流水线读取受保护 CI/CD 变量。
- CVE-2026-78252(8.2):Markdown JSON 表格渲染器 XSS,影响版本追溯到 15.3。
一次升级全部解决,没有理由只修主角。
参考
- https://docs.gitlab.com/releases/patches/patch-release-gitlab-19-3-2-released/
免责声明:本文仅用于安全研究与学习,未经授权不得用于任何非法渗透测试活动。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:Kratos Sec Kratos Kratos《GitLab CVE-2026-85706 未授权任意文件读取分析:一个末尾斜杠打穿的满分漏洞》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论