CI/CD 配置为何能触发服务端 RCE?
我一直觉得 .gitlab-ci.yml 是全天下最人畜无害的文件——纯文本、不执行、顶多写错缩进让流水线红一片。直到看见 CVE-2026-97229 这个被 CISA 列入已知被利用漏洞目录、CVSS 打到 10.0 的家伙,我才反应过来自己的直觉错在哪儿:我一直在想“这段配置会不会在 Runner 里干坏事”,却没想过它压根不需要走到 Runner。
问题出在“谁在读这份配置”
GitLab CI 的语义里,rules:if、only:variables 这些指令支持用 =~、!~ 做条件判断,比如按分支名匹配 feat|fix|chore 前缀。这段匹配不是交给 Runner 容器去跑,而是 GitLab 服务端在解析、预编译配置文件时,直接调用了底层的 Ruby 正则引擎。构造得足够恶劣的模式会让编译器发生整数溢出,接着就是内存崩塌、转为执行攻击代码。
所以沙箱边界在这里根本没用上。攻击者不需要把代码合并进去,也不需要任何一条流水线真的跑起来,只要提交一份包含畸形正则的 CI 配置就够了。开放注册或者存在公开项目的实例,推一个分支就能把载荷递到服务端进程面前——Puma 或 Workhorse 相关工作进程就是被打的那一侧。
升级时别只盯着应用版本
官方在 19.4.2、19.3.5、19.2.8 几个分支发了修复。但我看到一句很关键的提醒:这次修复依赖发行包里打包的受修补 Ruby 环境。如果你们用的是自定义容器镜像或者源码编译的 GitLab,只更新应用代码是不够的,得确认底层 Ruby 的正则引擎一起跟上,不然等于白升。
暂时升不了的话,能做的止血动作也就那几个:关掉公网自由注册,把默认项目可见性收到 Internal 或 Private,再在反向代理或 WAF 层对提交到 CI Lint 接口的超长正则字段做检测设限。但 WAF 盖不住走 Git 协议推送触发的解析,它只是临时手段,不能拿来替代补丁。
顺手翻一遍仓库里的规则
我在想这件事最别扭的地方是:审计的对象不是代码,而是那一堆看着像配置的字符串。所以我的做法是先跑只读扫描,找那些括号嵌套特别深、单行正则特别长的规则,先告警不动手拦——遗留流水线里常有语义化版本匹配或很长的路径排除,直接阻断会误伤正常构建。发现的异常再人工看,重点看是不是有大量重复字节和不平衡语法。
说到底,CI 配置文件也是对外暴露的输入,只要它会被服务端解析,它就该按不可信数据处理。

参与讨论
原来配置文件本身就能成为攻击入口