GitLab给四个漏洞打了CVE编号,其中两个的CVSS 3.1评分是10.0——满分,而且是不需要登录就能触发的两个。这四个编号分别是CVE-2026-89078、CVE-2026-93577、CVE-2026-97229和CVE-2026-110264。问题出在Ruby编译正则表达式的过程里:精心构造的正则会让内存出错,进而可能被用来在GitLab服务器上执行远程代码。
![]()
Ruby随后发布了正则编译相关的修复。按Ruby安全公告的说法,触发路径是把不受控的输入交给Regexp.new、Regexp.union或Regexp.compile,或者把它插值进正则字面量,写成/#{input}/这种形式。如果应用里每一条正则都是开发者手写的,公告的判断是不受影响。
版本支持上出现了例外。Ruby 3.3已经处于安全维护阶段,本来不该再收功能之外的改动,但因为GitLab给这些问题分配了CVE,3.3还是拿到了修复。Ruby 3.2及更早版本没有补丁——3月发布的3.2.11是3.2的最后一个版本,之后不再有安全修复,官方建议迁到3.4或4.0。
这批CVE编号归GitLab所有。Ruby尚未为这些问题申请自己的CVE,并表示仍在讨论是否把它们当作漏洞处理,理由是Ruby长期以来就把"用不可信输入编译正则"视为不安全行为。
先确认生产环境到底跑的是哪个版本
Ruby的版本号经常不止写在一个地方。在服务器上的Rails控制台里查RUBY_VERSION,能知道实际运行的是哪一个。要找出所有被钉住的位置,需要逐个看:版本管理器的配置文件(.ruby-version或.tool-versions)、Gemfile里如果有ruby那一行、Docker基础镜像的标签,以及单独构建的CI镜像。
找运行时编译正则的位置,可以先做一次粗略搜索:
- grep -rnE 'Regexp\.(new|union|compile)|/[^/]*#\{' app lib
插值那条模式会带出不少噪音。对每一个命中点,要判断的是:模式里有没有任何一部分来自用户、请求参数,或者用户可编辑的数据库字段。
一个真实项目里的检查结果
在一个Rails项目上跑完这套检查,Ruby版本被设在三个地方,每个地方的修法都不一样:
- 版本管理器文件钉着一个较旧的4.0补丁版本,需要手动升上去。
- 生产Dockerfile用的是浮动标签,类似ruby:4.0-alpine。官方镜像包含4.0.9之后,重新构建就会拿到它,但重建后值得进镜像里确认一下补丁版本号。
- CI跑在一个单独构建的镜像上,只有有人重跑构建流程时才会更新——这一处最容易被忘掉。
搜索还找出三处用运行时字符串拼正则的地方。模式来自数据导入文件和后台表单,其中一处用了插值,值看起来是内部的,但没有逐个追调用方。这三处都无法从公开API触达,风险不高,但能上传导入文件的人就控制了那些模式。无论哪种情况,升级Ruby都是解法。
升级当天,CI先挂了
在4.0.9发布当天就升上去,CI以两种方式断掉。跑在CI Docker镜像里的任务失败,因为构建要求一个4.0.9的镜像,而Docker Hub发布ruby:4.0.9-alpine之前这个镜像根本不存在。用ruby/setup-ruby的任务则报"Unknown version 4.0.9"——每个setup-ruby版本只认识它发布时已存在的Ruby版本,而项目钉的是旧版。解法是把setup-ruby的钉版升到当天早上发布、已支持4.0.9的v1.334.0,同时把镜像标签留在4.0,等官方镜像跟上后再重建。
顺序上的教训是:先等基础镜像,再升setup-ruby,最后改.tool-versions。
漏洞本身长什么样
问题在Onigmo里,也就是Ruby内置的正则引擎。其中一个是双重释放——内存在解析失败后被释放两次,可能造成损坏,触发条件是模式里出现选择分支(两个选项之间的|)之后解析失败。其余几个是整数溢出:编译后模式的尺寸、以及重复次数很多的模式所用的内部ID,数值超出类型范围后回绕。编译期间的内存损坏,正是把一条坏模式变成代码执行的那一环。
GitLab已在19.4.1、19.4.2和19.5.1中修掉了它随附的Ruby,并向更老的GitLab版本做了回移。GitLab.com和GitLab Dedicated已经打好补丁。自建GitLab的,要更新GitLab本身——它的软件包自带打过补丁的Ruby;源码安装的还需要另外升级Ruby。
该做什么
升到Ruby 4.0.9、3.4.12或3.3.13及更高版本,并且覆盖前面找到的每一处:版本管理器文件、Gemfile、Docker基础镜像、CI镜像。自建GitLab的,更新到已修复的GitLab版本。然后回头看那些用用户输入拼出来的正则,问一句:它真的需要是正则吗?普通字符串匹配往往就够了。
暂时升不了的,公告给出的唯一缓解办法是不要用不可信输入编译正则。有两个常见做法被明确排除:Regexp.timeout没用,它限制的是匹配跑多久,而这些bug发生在模式编译阶段;限制模式长度也不可靠,因为有些bug用很短的模式就能触发。公告没有说明用Regexp.escape转义输入是否足够,所以升级仍是稳妥答案。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.