真机环境:GitLab CE 19.5.3 / Rocky Linux 9.8
2026 年 11 月
目 录
[2.1 为什么运维必须当天响应](#2.1 为什么运维必须当天响应)
[四、30 秒自查:版本三连](#四、30 秒自查:版本三连)
[4.1 方法一:读版本文件(最快,1 秒)](#4.1 方法一:读版本文件(最快,1 秒))
[4.2 方法二:查 RPM 包(同时确认安装形态)](#4.2 方法二:查 RPM 包(同时确认安装形态))
[4.3 方法三:Web 页面(无服务器登录权限时)](#4.3 方法三:Web 页面(无服务器登录权限时))
[4.4 判定](#4.4 判定)
[5.1 日志排查](#5.1 日志排查)
[5.2 项目侧排查](#5.2 项目侧排查)
[5.3 时间线对照](#5.3 时间线对照)
[6.1 第一步:先备份](#6.1 第一步:先备份)
[6.2 第二步:升级](#6.2 第二步:升级)
[6.3 第三步:验证](#6.3 第三步:验证)
[七、加固建议与应急 SOP](#七、加固建议与应急 SOP)
[7.1 立即加固](#7.1 立即加固)
[7.2 若确认已被攻破](#7.2 若确认已被攻破)
[7.3 通用 SOP:满分漏洞当天处置流程](#7.3 通用 SOP:满分漏洞当天处置流程)
一、漏洞速览
2026 年 10 月 12 日,GitLab 发布紧急补丁版本,修复了一个 CI/CD 正则编译器中的整数溢出漏洞 CVE-2026-97229。该漏洞允许未经身份验证的攻击者在服务器上执行任意代码,CVSS 3.1 评分 10.0 满分。10 月 20 日至 21 日,安全人员观测到攻击者对 Alpine Linux 的 GitLab 实例发起真实攻击;10 月 29 日 CISA 将其列入已知被利用漏洞(KEV)目录;10 月 30 日 Metasploit 合并了公开利用模块------从补丁发布到武器化不到 20 天,属于典型的「满分漏洞 + 已武器化」组合,必须当天处置。
|------------|--------------------------------------------------------------------------------------------------------|
| 项目 | 内容 |
| CVE 编号 | CVE-2026-97229 |
| severity | CVSS 3.1 10.0(Critical) |
| 漏洞类型 | Onigmo 正则引擎整数溢出 → 堆越界写 → 未认证 RCE |
| 受影响版本 | CE/EE:19.2 < 19.2.8;19.3 < 19.3.5;19.4 < 19.4.2;19.5.0 |
| 修复版本 | 19.2.8 / 19.3.5 / 19.4.2(10-12);19.5.1 / 19.4.3 / 19.3.6 / 19.2.9 / 19.1.9 / 19.0.10 / 18.11.13(10-21) |
| 在野利用 | 已确认(Alpine Linux 事件、CISA KEV、Metasploit 模块) |
| 利用前提 | 部署构造的 CI/CD 配置;投递请求本身无需认证 |
二、漏洞原理
这个问题不在 GitLab 的上层业务代码,而在其底层依赖的 Ruby 正则表达式编译器 Onigmo。GitLab 在校验 CI/CD 配置中的正则表达式时,直接调用打包的 Ruby 3.3/3.4 的 Regexp 编译接口,对模式长度与重复计数没有任何限制。
Onigmo 在编译量词(重复)节点时,用有符号 32 位整数计算「编译长度 × 重复次数」,并拿这个乘积与固定上限比较来决定是否展开重复。这个比较没有溢出保护:当乘积越过 32 位整数上限发生回绕,变成一个很小的值甚至负值时,负值同样能通过上限检查------本应拦截超大重复的保护逻辑被它自己要拦的溢出绕过了。编译器随后按回绕后的小值分配缓冲区,却写入完整的指令流,跳转偏移同样回绕,最终形成堆越界写。
攻击者把构造好的正则写进 CI/CD 配置(.gitlab-ci.yml 的 rules/only/except 等位置),触发 GitLab 服务进程编译该正则,即可在服务进程内执行任意代码。正因漏洞出在 2014 年起就存在的底层引擎,GitLab 10 月 21 日的补丁一口气回溯修复了 18.11 至 19.5 共七个版本线。
2.1 为什么运维必须当天响应
- 10-12 补丁发布 → 10-20 实际攻击 → 10-29 进 KEV → 10-30 公开 Metasploit 模块,时间线一路压缩。
- Metasploit 模块瞄准的是官方 Omnibus Docker 镜像(amd64),即自建实例的主流部署形态------照着文档装的实例就是靶子。
- GitLab 通常承载全部源码与 CI/CD 流水线,一旦 RCE 等于供应链沦陷:源码、构建凭据、部署密钥全部暴露。
三、真机实测环境
本文所有命令均在以下真机环境实测执行:
|------------|------------------------------------------------------------|
| 项目 | 配置 |
| 主机 | 内网虚拟机 gitlab(10.10.10.76) |
| 操作系统 | Rocky Linux 9.8 |
| GitLab | CE 19.5.3(Omnibus RPM 安装,gitlab-ce-19.5.3-ce.0.el9.x86_64) |
| 服务形态 | gitlab-ctl 管理共 15 个 run 服务,非容器部署 |
| 资源 | 7 GB 内存,/ 分区 46G(已用 26%) |
先说实测结论:这台机器的 19.5.3 高于 10 月 21 日发布的修复版本 19.5.1,已包含 Onigmo 整数溢出补丁,不受 CVE-2026-97229 影响。因此本文演示的重点是「如何快速自查版本、如何确认没被打过、以及如果版本不对怎么补」------这正是大多数读者现在需要做的事。
四、30 秒自查:版本三连
判断是否受影响,本质就是确认当前版本号。Omnibus 部署有三种等价查法,按速度排序:
4.1 方法一:读版本文件(最快,1 秒)
Omnibus 安装把当前版本明文写在 VERSION 文件里,无需任何 GitLab 命令:
cat /opt/gitlab/embedded/service/gitlab-rails/VERSION
19.5.3
4.2 方法二:查 RPM 包(同时确认安装形态)
rpm -q gitlab-ce # CE 版;EE 环境换成 rpm -q gitlab-ee
gitlab-ce-19.5.3-ce.0.el9.x86_64
4.3 方法三:Web 页面(无服务器登录权限时)
浏览器访问 http://实例地址/help,页面右上角显示版本号;或用管理员 Token 调 API:
curl -s -H "PRIVATE-TOKEN: <token>" http://10.10.10.76/api/v4/version
{"version":"19.5.3","revision":"xxxxxxxxxxxx"}
4.4 判定
|---------------------------|----------------------------------------|
| 你的版本 | 结论 |
| 19.5.1 ~ 19.5.x(≥19.5.1) | 已修复,与实测机 19.5.3 同状态,无需升级 |
| 19.5.0 | 受影响,升到 19.5.1 以上 |
| 19.4 < 19.4.2 | 受影响,升到 19.4.2 以上(建议直接 19.4.3) |
| 19.3 < 19.3.5(含 19.3.4) | 受影响,升到 19.3.5 以上;19.3.4 另有缺陷,直接 19.3.6 |
| 19.2 < 19.2.8 | 受影响,升到 19.2.8 以上 |
| 18.x 旧版本线 | 受影响(Onigmo 缺陷可回溯至 7.0),升 18.11.13 |
五、排查是否已经被打过
版本没问题只是第一步。如果实例在补丁前长期暴露公网,还应做利用痕迹排查。Metasploit 模块需要一名能向公开项目提交代码的用户来布置攻击载荷,但真正触发漏洞的请求无需认证------所以日志里重点是「陌生来源的 CI 配置解析与推送」。
5.1 日志排查
GitLab 日志目录
ls /var/log/gitlab/gitlab-rails/
扫描主日志中的正则编译相关异常
grep -iE 'regexp|onigmo|compile' /var/log/gitlab/gitlab-rails/production.log
API 日志里找异常来源 IP(10-12 补丁之前的时段重点看)
grep -oE '"remote_ip":"\^"+"' /var/log/gitlab/gitlab-rails/api_json.log | sort | uniq -c | sort -rn | head
实测机上对 production.log 的正则/编译关键字扫描结果为空,无异常记录。读者按同样的关键字扫自己实例的日志,若发现陌生 IP 频繁触发 CI 配置解析类请求,需进一步核对对应项目的 .gitlab-ci.yml 是否被插入过可疑正则。
5.2 项目侧排查
- 审计公开项目中 .gitlab-ci.yml 的变更历史,重点看 rules、only、except 中的正则是否被改动过。
- 核对系统用户列表有无陌生账号(利用模块需要可提交代码的账号)。
- 检查 CI Runner 注册记录与流水线运行记录有无异常任务。
5.3 时间线对照
把「实例暴露公网的时间段」与攻击时间线对照:10-12 补丁发布前就暴露公网且从未打补丁的,按被攻破对待,处置见第七章;补丁后才上线的可以直接进入常规加固。
六、修补流程:从旧版本升到安全版
以一台受影响的 19.5.0 为例(实测机 19.5.3 已是安全版,以下为标准流程,已在实测机验证命令语法)。Omnibus 部署的升级就是换 RPM 包,但必须按顺序做。
6.1 第一步:先备份
本次补丁包含数据库迁移,单节点实例升级期间会停机。升级前必须做全量备份:
gitlab-backup create STRATEGY=copy
cp /etc/gitlab/gitlab-secrets.json /backup/
cp /etc/gitlab/gitlab.rb /backup/
gitlab-secrets.json 丢了备份等于数据全废(加密列无法解密),务必单独复制。
6.2 第二步:升级
Rocky/RHEL 系(实测环境)
dnf update gitlab-ce -y
或指定目标版本
dnf install gitlab-ce-19.5.3-ce.0.el9 -y
Debian/Ubuntu 系对应 apt install gitlab-ce=版本号。升级过程 GitLab 自动执行 reconfigure 与数据库迁移,单节点停机时间取决于数据量,一般 10 到 30 分钟。
6.3 第三步:验证
gitlab-ctl status # 15 个服务应全部 run
cat /opt/gitlab/embedded/service/gitlab-rails/VERSION # 确认新版本号
gitlab-rake gitlab:check SANITIZE=true # 全面体检(耗时较长,耐心等)
run: gitaly: (pid 11337) ...; run:
run: gitlab-workhorse: (pid 11279) ...; run:
run: nginx: (pid 63647) ...; run:
(实测机 15 个 run 服务全部正常)
最后用浏览器登录确认:仓库可访问、流水线可运行、合并请求正常------补丁含数据库迁移,业务验证不能省。
七、加固建议与应急 SOP
7.1 立即加固
- GitLab 管理界面与 SSH 不暴露公网:远程访问走 VPN 或 IP 白名单,这是比任何补丁都靠前的防线。
- 开启自动安全更新或订阅 GitLab 官方安全公告,Critical 级公告当天响应。
- 最小权限:减少能向项目提交代码的账号,关闭闲置账号,开启 2FA。
- 备份常态化:gitlab-backup 定时任务 + 异机存放, secrets.json 单独保管。
7.2 若确认已被攻破
- 立即断网隔离,保留日志与现场。
- 按第六章完成升级后再恢复服务,防止二次沦陷。
- 轮换全部凭据:GitLab root 与用户密码、Runner Token、CI/CD 变量中存放的部署密钥、SSH 私钥、云 AccessKey------GitLab 沦陷意味着这些密钥全部视为泄露。
- 审计流水线历史,确认攻击者没有借 CI 通道横向渗透到部署目标。
7.3 通用 SOP:满分漏洞当天处置流程
- 看到 KEV / 官方 Critical 公告,先做资产清单比对:我有没有、什么版本、暴不暴露公网。
- 版本三连(VERSION 文件 / rpm / API)30 秒定性。
- 受影响 → 备份 → 升级 → 验证,当天完成;完成不了先断公网。
- 不受影响 → 记录证据(版本截图与命令输出),进入常规巡检。
- 事后按第五章补一遍利用痕迹排查。
一句话总结:CVE-2026-97229 的可怕之处不在技术多新,而在武器化速度------补丁发布到 Metasploit 模块公开只用 18 天。自建 GitLab 的版本自查是 30 秒的事,别让它变成 18 天的事。