博客 / CVE 解读

CVE-2026-97229:GitLab「未经身份认证」RCE 背后被省略的附加条件

GitLab 给出的 CVSS 10.0 评分和 CISA 的 KEV 名单都称其为未经身份认证。但唯一两个真正让它跑起来的人,都是先有了一个 GitLab 账号。这里说明这一点为何重要。

已完成信息安全方向博士研究生学业已通过校内答辩 · 最终学位论文答辩尚待进行

纸面上零凭据,现实中却要先有一个账号

零。这是 GitLab 自己给出的 CVE-2026-97229 CVSS 向量所要求的凭据数量:AV:N/AC:L/PR:N/UI:N,满分 10.0,无需任何权限即可执行任意代码。一。这是唯二两位真正让这个漏洞跑起来的人——各自独立——在发出那个真正起作用的请求之前,所需要的账号数量。这两个数字都是真的,它们描述的是同一次攻击里两个不同的步骤。

CVE-2026-97229 是 GitLab CI/CD 正则表达式编译器中的一个严重整数溢出漏洞,已于 2026 年 10 月 12 日在 19.2、19.3 和 19.4 分支上修复。[1] GitLab 自己的安全公告描述的正是这个评分所暗示的内容:在 CI/CD 配置中构造的正则表达式,由未经身份认证的用户发送,在特定条件下会转化为服务器上的任意代码执行。[1] 不到三周后,CISA 将其加入已知被利用漏洞目录,确认它正被用于针对真实目标的攻击。[2] 这些说法都没有错,只是省略了唯一一个真正能说明您实际暴露程度的细节:要走到那个未经身份认证的步骤之前,攻击者首先需要一个账号。

GitLab 到底发布了什么,又是什么时候发布的

GitLab 把修复版本的发布日期定在 2026 年 10 月 12 日,为三个受影响分支分别发布了 19.2.8、19.3.5 和 19.4.2——19.2 版本早于 19.2.8、19.3 版本早于 19.3.5、19.4 版本早于 19.4.2 均受影响。[1] 在公告发布之前,GitLab.com 就已经运行在修复后的构建版本上,而 GitLab Dedicated 的客户则完全不需要做任何事。[1] 报告该漏洞的研究者使用的是 HackerOne 用户名 joaxcar,他还另外报告了一个中等严重性的开放重定向漏洞 CVE-2026-7767,该漏洞在同一版本中修复。[1]

事后有一个细节发生了变化,值得在您核对自己的版本号之前先了解一下。GitLab 最初的发行说明把 19.3 分支的修复版本标注为 19.3.4;第二天的报道指出,那个构建版本实际修复的是一个与合并请求页面无关的漏洞,而 19.3.5 才是这个 CVE 真正的修复版本,目前的公告也已经反映了这一点。[8] 在那之后,事情进展得很快:Rapid7 在披露约一天后报告了一次独立且成功的复现,大约四天后出现了一个署名的公开概念验证(PoC),而 CISA 确认被利用的记录则在原始补丁发布约十七天后出现。[1][2][4][7] 这不是本文要讲的主线故事,但仍然值得停下来想一想:当补丁本身就告诉了攻击者该往哪里看时,像 GitLab 这样产品里的一个 CVSS 10.0 漏洞,会很快被武器化。

这个正则表达式引擎执行的是您的命令,而不是在匹配您的字符串

我本人没有亲自跑过这个漏洞。以下内容全部来自 Jonah Burgess(GitHub:jburgess-r7)在补丁发布约四天后公开的概念验证——他本人明确将其称为“初版 PoC 发布”——以及 Rapid7 另行报告的复现结果,还有 GitLab 自己的安全公告,均截至 2026 年 11 月 2 日读取。[1][3][4] 触发点藏在 GitLab 的 CI-lint 接口背后,这正是 GitLab 自己在任何流水线运行之前用来校验 .gitlab-ci.yml 文件的那个接口。只要向它提交一个在 CI/CD 配置中构造的正则表达式,并配合一个路径使用了特定 UTF-8 命名技巧的仓库分支,正则表达式编译器内部的长度计算就会溢出。[3]

这次溢出不仅仅会让 worker 崩溃——尽管它确实经常也会导致崩溃。它会把正则表达式引擎的执行流程重新定向,直接导向一个会执行任意命令的函数。[3] 该命令以 GitLab 低权限的服务账号“git”身份运行,既不是管理员,也不是主机上的 root。[3] 这为破坏范围设置了一个真实的上限,值得明确说出来:这是服务器端代码执行,不是通向完整管理员控制权的捷径。不过,从一个“git”shell 出发,攻击者已经能拿到不少值得拿的东西了,从仓库内容开始,再到该账号能读取的任何 CI 密钥。

// The CI-lint trigger, reconstructed from Burgess's public PoC. Paraphrased
// shape of the chain -- not the working payload itself.

1. Attacker-controlled branch is pushed to a public project, with a
   specially-named (UTF-8) path and a crafted regex in .gitlab-ci.yml.
2. An unauthenticated request hits GitLab's CI-lint endpoint, which
   validates that regex before any pipeline actually runs.
3. The regex compiler's internal length accounting overflows -- an
   integer wraps where it shouldn't.
4. Execution inside the regex engine is redirected into a function that
   runs an OS command, as the low-privilege "git" service account.
5. On a vulnerable build, the worker process frequently crashes right
   after (a bare HTTP 502 is a side effect, not proof by itself).

发生在“未经身份认证”那一步之前的步骤

这正是每一条 CVSS 10 新闻标题都会省略的细节。Burgess 自己的 README 直白地列出了前提条件:一个能在目标实例上向公开项目推送分支的普通账号;一个恰好运行官方构建 gitlab/gitlab-ce:19.4.1-ce.0 的目标(任何其他构建都需要重新计算二进制偏移量);大约 9 GiB 的空闲内存;以及如果想要命令输出结果,还需要能连接到一个监听端的网络可达性。[3] 真正触发溢出的那个 CI-lint 请求本身确实不需要任何令牌、Cookie 或授权头——这一部分确实是未经身份认证的。[3][4] 只是它不是第一步。

据 Help Net Security 的报道,Rapid7 在披露约一天后,针对未打补丁的官方 GitLab CE 19.4.1 镜像独立复现出了可用的代码执行(同样的输入在已打补丁的 19.4.2 镜像上失败),并描述了同样的门槛:一个“普通账号”推送构造好的分支,这种方式在“允许创建公开项目的自托管实例”上可行。[4] 两个独立的团队分别单独工作,却得出了相同的前提条件。我要指出一点记录中其实并未明确说明的内容,因为把两者合并起来确实很有诱惑力:这个 PoC 的 GitHub 用户名带有“-r7”后缀,其 README 本身也链接到了“Rapid7 对同一漏洞的技术分析”。没有任何来源直接说明这种关联,所以我按记录实际呈现的方式来看待 Burgess 的 PoC 和 Rapid7 的复现:这是对同一前提条件的两次独立确认,而不是一次。

PoC 作者对其局限性也很坦诚:在启用 ASLR 的那个唯一测试构建上,10 次尝试全部成功;同样的输入在已打补丁的 19.4.2 编译器上返回 false;作者直接表示“对其他受影响配置和可达路径的分析仍在进行中”,并且该 PoC“并不能证明在实际入侵中存在真实利用”。[3] 一次成功的触发往往只会返回一个裸的 HTTP 502,因为 worker 会在执行命令后立刻崩溃,所以仅凭一个 502、而没有其余模式配合,什么都证明不了。[3] 请记住这个区别,它决定了您能从自己的日志中真正读出什么。

“未经身份认证”到底能延伸到多远

那么到底算不算未经身份认证?取决于您问的是哪一条边界——两者都对。GitLab 的 CVSS 向量将这个易受攻击的组件评为 PR:N,即不需要任何权限,这对它实际评估的内容而言是准确的:CI-lint 请求本身确实不检查任何凭据。[1] 但“该组件不需要身份认证”和“整条攻击链不需要身份认证”是两个不同的说法,而唯二两个在实践中真正让它跑起来的具名团队——Burgess 和 Rapid7——都各自独立地认为,这条攻击链在那个请求之前还需要一步:一个能把分支推送到公开项目的账号。[3][4] GitLab 的严重性评级并没有错,它只是回答了一个比标题所暗示的范围更窄的问题。

这个前提条件到底是微不足道还是一道真正的障碍,取决于大多数管理员并不会当作针对这个 CVE 的缓解措施来看待的那些设置。GitLab 自己的文档说,在自托管实例上,默认情况下任何访客都可以自助注册,并建议在任何面向公网、且不期望开放注册的实例上关闭这一功能。[5] 文档还说,自 GitLab 13.6 起,新实例的默认设置就是需要管理员批准新注册,所以一个相当新、配置也相当合理的实例,在陌生人获得账号之前,本来就已经有人在把关了。[5] 项目的可见性,以及谁可以创建公开项目,是管理员可以单独配置的另一项设置。[5] 以上这些都不是补丁,但它们都会改变有多少陌生人能够触及这个漏洞中未经身份认证的那一半。

// For a stranger to turn this into RCE against your instance,
// three things have to be true at once:

1. Your instance allows self-registration, OR the attacker already
   holds any ordinary account on it.
2. That account (or self-registration) can create, or push a branch
   to, a PUBLIC project -- not just a private one.
3. The instance is still running an unpatched build
   (before 19.2.8 / 19.3.5 / 19.4.2) and is reachable from wherever
   the attacker is.

Close registration, restrict who can make projects public, or patch --
any one of the three breaks the chain. Patching is still the only fix
that doesn't depend on a setting staying the way you left it.

利用已确认,几乎其他一切都没有确认

CISA 于 2026 年 10 月 29 日将 CVE-2026-97229 加入其已知被利用漏洞目录,命名为“GitLab Community Edition and Enterprise Edition Integer Overflow or Wraparound Vulnerability”,并依据有约束力的运营指令 BOD 26-04,将强制处置的截止日期定为 11 月 1 日。[2] 这个加入日期本身并不是在说明利用实际从何时开始。KEV 条目的记录时间经常滞后于真实的首次利用,而本文查阅的任何来源都没有说明针对这个漏洞的攻击实际从何时开始。请把“10 月 29 日”理解为 CISA 确认这件事的日期,而不是攻击本身的时间戳。

以下是这份记录中“已确认被利用”究竟意味着什么的诚实尺度,这里值得讲清楚,因为实际记录比标题所暗示的要薄得多:本文查阅的任何来源——无论是 CISA 自己的条目、GitLab 的公告,还是 BleepingComputer 的相关报道——都没有公布任何入侵指标、具名受害者,或者关于观察到的攻击实际做了什么的任何技术层面描述。[1][2][6] BleepingComputer 的后续报道引用了 GitLab 自己发布的威胁检测规则,以及 Shadowserver 统计出的大约 2800 台暴露在互联网上的 GitLab CE 实例,而这就是目前所能获得的全部公开细节。[6] 这里的“已确认被利用”所依据的是 CISA 这个具名权威机构的列表,而不是本文可以独立核实的攻击证据。这是一个合理的判断依据,但它也是我能对攻击本身诚实说出的上限。

补丁、规则,以及本周应该检查的事项

修复版本精确到位:19.2.8、19.3.5 和 19.4.2,均于 2026 年 10 月 12 日发布。[1] 如果您的 19.3 分支显示的是 19.3.4,那个构建修复的是一个与合并请求页面无关的漏洞,并不是这个 CVE;您真正需要的是 19.3.5。[8] 随补丁一起,GitLab 还为自托管实例发布了六条专门针对这个问题命名的新威胁检测规则:Onigmo native worker crash、GraphQL cilint hostile regexp、ci regexp oversized non-ascii path、ci regexp guard rejection、ci lint workhorse worker crash 和 ci lint scripted client error burst。[1] 如果您自己的监控系统或 GitLab 的检测功能出现其中任何一条,请把它当作值得追查的信号,而不是噪音。

GitLab 自己的披露政策规定,对于已修复的漏洞,会在修复版本发布 90 天后公布工单跟踪系统中的完整细节,按简单的算术推算,公告文本之外的内容大约会在 2027 年 1 月中旬公开——这不是 GitLab 自己给出的日期,只是其自身 90 天规则推算出的结果。[1] 在那之前,以下是公开记录实际能支持大家去检查的内容。

Patch and verify, in order:
  [ ] Confirm the build actually RUNNING, not just what the changelog
      says: 19.2.8 / 19.3.5 / 19.4.2 or later.
  [ ] If self-managed, review sign-up restrictions: is self-registration
      open, and is admin approval of new accounts actually enabled?
  [ ] Review who can create or push to a PUBLIC project on your instance
      -- that setting is the other half of this bug's real exposure.
  [ ] Enable and watch GitLab's six named threat detections for this CVE,
      especially "ci lint workhorse worker crash" and "ci regexp guard
      rejection".
  [ ] Check CI-lint access logs for requests with no auth header paired
      with recently-pushed branches on public projects -- that pairing
      is this chain's fingerprint, a bare HTTP 502 is not.

CVSS 评分给漏洞打分,并不给您的实例打分

CVE-2026-97229 确实是一个真实的严重漏洞,GitLab 和 CISA 对严重性的判断都没有错。但这个数字本身无法告诉您的是,您的实例究竟是这条攻击链所瞄准的那种完全开放的类型,还是注册已关闭、项目可见性也被锁死的那种类型。对于一个即便是 CVSS 10.0、并且正被积极利用的漏洞来说,后一种实例是一个明显更小的目标。严重性评分评估的是一个组件,而暴露程度是您自身配置的一个属性,没有人会替您给它打分。

这正是我在做 Web 应用或 CI/CD 流水线审查时会去寻找的那种差距:不只是“这个补丁打了没有”,而是“考虑到这个实例实际的配置方式,某个漏洞里未经身份认证的那一半,到底能为攻击者在这里带来什么”。如果您运行的是自托管 GitLab,或者任何带有公网暴露部分的 CI/CD 表面,并且更愿意让人和您一起过一遍这条边界,而不是只凭一个 CVSS 分数的说法,那正是我所提供的评估服务。告诉我您的实例实际是怎么配置的,我们再来确定工作范围。

参考来源

需要在您自己的产品中检查这个问题吗?

Web 应用与 API 渗透测试

想学会自己发现这类漏洞吗?

Web 应用渗透测试课程(一对一)
全部文章

08联系

即将上线处理用户数据的产品?

给我发一份简要需求。我会在三天内回复问题或方案大纲,价格在开始前以书面形式确定。