Ruby 官方於 2026 年 10 月 23 日(UTC)同時釋出 4.0.9、3.4.12 與 3.3.13 三個版本,修補內建正規表達式引擎 Onigmo 的三項記憶體損壞漏洞。這三項漏洞正是 GitLab 自 9 月 23 日以來連續發布四個重大 CVE 的共同源頭,其中兩個 CVE 被 GitLab 評為 CVSS 10.0 滿分,且無需登入即可觸發。
過去一個月,資安圈的注意力集中在 GitLab。GitLab 於 9 月 23 日、10 月 12 日與 10 月 21 日三度發布重大修補,每一次都是透過修補自家打包的 Ruby 直譯器來解決問題。直到 Ruby 官方在 10 月 23 日發布公告,外界才得以確認,問題不在 GitLab 的 Rails 程式碼,而在 Ruby 語言本身的正規表達式編譯器。Ruby 在公告中明確指出,任何以攻擊者可控字串編譯正規表達式的應用程式都受影響,不限於 GitLab。
這起事件對台灣的 Ruby 使用者有兩層意義。第一層是技術面,凡是使用 Regexp.new、Regexp.compile、Regexp.union 或字串內插 /#{input}/ 處理使用者輸入的 Ruby 應用程式,無論是 Rails 網站、Sinatra 服務、背景工作程式或命令列工具,都需要升級。第二層是治理面,Ruby 官方目前未為這些修補申請自己的 CVE 編號,現有的四個 CVE 紀錄只列 GitLab 為受影響產品,依賴 CVE 比對的弱點掃描工具將無法標示出未修補的 Ruby 執行環境。
CyberQ 依據處理客戶的事件時間軸、三項漏洞與四個 CVE 的對應關係、受影響範圍與修補版本做彙整,並說明 CVE 缺口對弱點管理流程的影響。截至 10 月 23 日,四個 CVE 均未列入 CISA 已知遭利用漏洞目錄(KEV),也尚無公開的野外攻擊報告。
事件時間軸
三項修補中有兩項早在 GitLab 發布第一個 CVE 之前就已合併進 Ruby 的 master 分支,只是遲遲未回溯到穩定版本。這段「主幹已修、穩定版未修」的落差,是整起事件最值得注意的地方。
| 日期(UTC) | 事件 | 來源 |
|---|---|---|
| 2022 年 4 月 12 日 | Ruby 發布 CVE-2022-28738,Regexp 編譯過程的 double free,公告中已說明不信任的樣式本就不安全,但仍視為漏洞處理 | ruby-lang.org |
| 2024 年 4 月 23 日 | Ruby 發布 CVE-2024-27282,攻擊者資料進入正規表達式編譯器時可任意讀取記憶體位址 | ruby-lang.org |
| 2025 年 5 月 16 日 | PR #13332(修補過大重複量詞的 double free)合併至 master,後隨 Ruby 4.0.0 於 2025 年 12 月 25 日出貨 | GitHub ruby/ruby、Ruby 公告 |
| 2026 年 4 月 1 日 | Ruby 3.2 終止支援(EOL),Ruby 3.3 進入僅修補資安問題的安全維護階段 | Ruby 分支維護頁 |
| 2026 年 4 月 29 日 | PR #16801(拒絕溢位的 repeat ID)合併至 master,但未回溯至任何穩定版 | GitHub ruby/ruby |
| 2026 年 9 月 23 日 | GitLab 釋出 19.4.1、19.3.3、19.2.7,修補 CVE-2026-89078 與 CVE-2026-93577(CVSS 9.9,需登入) | GitLab 19.4.1 公告 |
| 2026 年 9 月 25 日 | PR #19020(避免編譯器程式大小運算溢位)由 hsbt 提出並合併至 master | GitHub ruby/ruby |
| 2026 年 10 月 12 日 | GitLab 釋出 19.4.2、19.3.5、19.2.8,修補 CVE-2026-97229(CVSS 10.0,無需登入),修補清單出現「Ruby regexp program size patch」字樣 | GitLab 19.4.2 公告 |
| 2026 年 10 月 21 日 | GitLab 釋出 19.5.1、19.4.3、19.3.6、19.2.9、19.1.9、19.0.10、18.11.13,修補 CVE-2026-110264(CVSS 10.0,無需登入,影響 7.0 以後所有版本),並發布兩條偵測規則 | GitLab 19.5.1 公告 |
| 2026 年 10 月 22 日 | Ruby 4.0.8 以「例行更新」名義釋出,含 #19020 但不含 #16801 | ruby-lang.org |
| 2026 年 10 月 23 日 03:11 | Ruby 發布正式公告並釋出 4.0.9、3.4.12、3.3.13,三個版本皆含全部三項修補 | Ruby 公告 |
四個 CVE 全部由使用 joaxcar 帳號的研究者 Johan Carlsson 透過 GitLab 的 HackerOne 漏洞獎勵計畫回報。Ruby 官方公告特別致謝他發現 #19020 所修補的整數溢位,並感謝 GitLab 通報與協調修補。
三項漏洞如何對應到四個 CVE
Ruby 公告以一張對照表說明每個 GitLab CVE 背後的 Ruby 修補,以及各系列首次納入該修補的版本。四個 CVE 中有兩個(CVE-2026-93577 與 CVE-2026-97229)對應同一項修補,差別在於 GitLab 端的觸發路徑是否需要登入。
| GitLab CVE | Ruby 端的問題 | Ruby 修補 | Ruby 4.0 | Ruby 3.4 | Ruby 3.3 | GitLab CVSS 3.1 | 是否需登入 |
|---|---|---|---|---|---|---|---|
| CVE-2026-89078 | 樣式在 alternation 之後解析失敗時發生 double free | #13332 | 4.0.0 | 3.4.12 | 3.3.13 | 9.9 | 是 |
| CVE-2026-93577 | 編譯後樣式的大小計算發生整數溢位 | #19020 | 4.0.8 | 3.4.12 | 3.3.13 | 9.9 | 是 |
| CVE-2026-97229 | 同上,GitLab 端為 CI/CD 設定的觸發路徑 | #19020 | 4.0.8 | 3.4.12 | 3.3.13 | 10.0 | 否 |
| CVE-2026-110264 | 含大量重複的樣式導致內部 ID 溢位 | #16801 | 4.0.9 | 3.4.12 | 3.3.13 | 10.0 | 否 |
正規表達式在 Onigmo 中並非逐字解讀,而是先被解析成語法樹,再編譯成一段供內部虛擬機執行的位元組碼程式。編譯過程需要計算大小、偏移量與各種內部計數器,這些以 C 語言撰寫的運算一旦出錯,就可能讓編譯器產出會讀寫緩衝區以外記憶體的程式。三項漏洞的技術性質如下,說明僅限於公開修補紀錄所揭露的範圍,不涉及攻擊細節。
Double free(#13332,CVE-2026-89078)
當樣式在 alternation 之後以特定方式解析失敗,Onigmo 會在解析與編譯階段各釋放同一塊記憶體一次。重複釋放會破壞記憶體配置器的管理資訊,屬於典型的程式碼執行途徑。修補確保該節點只被釋放一次。
程式大小整數溢位(#19020,CVE-2026-93577 與 CVE-2026-97229)
大範圍的有界重複會讓編譯器將多個大小相乘,乘積可能超過 C 語言 int 的上限,使原本應該失敗的大小檢查反而通過。串接的重複也可能讓編譯後程式超過 INT_MAX,寫入其中的跳躍偏移量因而繞回負值。修補改以除法比較大小以避免溢位,並在輸出時限制實際的程式大小。
內部 ID 溢位(#16801,CVE-2026-110264)
Onigmo 為樣式中的每個重複指派一個內部 ID,該欄位為有號 16 位元。依據 GitLab 偵測文件的說明,超過 32,767 個通用重複的樣式會讓 ID 超出上限,於比對階段造成越界寫入。修補在 repeat 與 null-check ID 即將溢位時明確拋出錯誤。
三項修補的程式碼變動都不大。依 Safeguard 整理的 GitHub 資料,#13332 改動 2 個檔案(新增 16 行、刪除 7 行),#19020 改動 5 個檔案(新增 34 行、刪除 6 行),#16801 改動 4 個檔案(新增 20 行)。改動規模小,正是 GitLab 得以自行為 Ruby 3.3、3.4 與 4.0 打上修補、不必等待 Ruby 官方釋出的原因。
不信任的樣式如何進入 GitLab 的編譯器
GitLab 的偵測規則描述了兩條路徑。CI 路徑是 .gitlab-ci.yml 中 rules: 區段的 regexp 子句,GitLab 會在 regexp_matcher.rb 中編譯這些使用者提供的樣式。另一條路徑則是 Org mode 文件渲染,GitLab 使用 org-ruby gem 處理 .org 檔案,而該 gem 會將文件中未經處理的 #+TODO: 關鍵字直接內插進 Regexp.new。這解釋了 CVE-2026-110264 為何無需帳號即可觸發,任何能讓 GitLab 渲染一份精心構造 .org 檔案的人,都能觸及 Onigmo。GitLab 在 19.5.1 公告中同時發布了「Org mode render error burst」這條偵測規則,佐證此路徑確為官方關注的攻擊面。
對其他 Ruby 開發團隊而言,這條路徑的啟示在於,有風險的呼叫不一定寫在自己的程式碼裡。任何從其解析內容建構正規表達式的第三方 gem,都可能代為製造同樣的攻擊入口。
影響範圍與修補版本
Ruby 公告對受影響條件的界定很明確。只有在應用程式以攻擊者可控的字串編譯正規表達式時,才會觸及這些漏洞。只使用開發者自行撰寫的固定樣式的應用程式不受影響。
| 程式碼模式 | 是否受影響 | 說明 |
|---|---|---|
Regexp.new(params[:pattern]) | 是 | 使用者撰寫整個樣式 |
Regexp.compile(user_input) | 是 | Regexp.new 的別名 |
Regexp.union(list_from_request) | 視情況 | Regexp.union 會跳脫純字串,但 Ruby 公告將其列為風險來源,在查清傳入內容前應視為在範圍內 |
/#{input}/ 或 %r{#{input}} | 是 | 字串內插在執行期由輸入建構新樣式 |
/#{Regexp.escape(input)}/ | 風險較低 | 輸入被跳脫為字面比對,無法加入量詞或群組,仍須確認所有進入的內容都有跳脫 |
str.match?(/\A[a-z]+\z/),str 來自使用者 | 否 | 樣式固定,只有被比對的字串來自使用者 |
| 第三方 gem 從其解析的檔案、設定或標記建構樣式 | 是,若攻擊者可控該內容 | GitLab 的無需登入路徑即屬此類 |
常見會出現使用者自訂樣式的位置包括提供「使用正規表達式」選項的搜尋功能、日誌檢視器與管理後台篩選器,多租戶 SaaS 中由租戶設定的路由規則與允許或拒絕清單,接受設定檔規則的 CI、自動化與工作流程產品,將文件關鍵字轉為樣式的標記與文件渲染器,以及聊天機器人與告警規則中「訊息符合時」的自動化。
各 Ruby 系列的修補狀況
| Ruby 系列 | 維護狀態 | 完整修補版本 | 備註 |
|---|---|---|---|
| 4.0 | 正常維護 | 4.0.9 | 4.0.0 已含 double free 修補,4.0.8 加入程式大小修補,4.0.9 加入 repeat ID 修補 |
| 3.4 | 正常維護 | 3.4.12 | 3.4 系列首個含任何一項修補的版本 |
| 3.3 | 安全維護,預計 2027 年 3 月 31 日 EOL | 3.3.13 | 因 GitLab 已指派 CVE,Ruby 以「例外」方式納入修補,同時更新 resolv 預設 gem 至 0.3.2 以修補 CVE-2026-80212 與 CVE-2026-80213 |
| 3.2 及更早 | 2026 年 4 月 1 日起 EOL | 無 | 不會釋出修補,Ruby 建議遷移至 3.4 或 4.0 |
Ruby 4.0.8 的陷阱值得特別留意。 Ruby 4.0.8 於 10 月 22 日以例行更新名義釋出,比正式公告早一天。它包含 #19020 的程式大小修補,但不包含 #16801 的 repeat ID 修補,也就是說,升級到 4.0.8 的使用者對 CVE-2026-110264 仍未設防。4.0 系列的使用者應直接安裝 4.0.9。
GitLab 自架版的修補版本
| CVE | 受影響 GitLab CE/EE 版本 | 修補版本 |
|---|---|---|
| CVE-2026-89078、CVE-2026-93577 | 19.2 至 19.2.7 之前、19.3 至 19.3.3 之前、19.4 至 19.4.1 之前 | 19.4.1、19.3.3、19.2.7(9 月 23 日) |
| CVE-2026-97229 | 19.2 至 19.2.8 之前、19.3 至 19.3.5 之前、19.4 至 19.4.2 之前 | 19.4.2、19.3.5、19.2.8(10 月 12 日) |
| CVE-2026-110264 | 7.0 至 18.11.13 之前,以及 19.0 至 19.5 各系列對應修補版本之前 | 19.5.1、19.4.3、19.3.6、19.2.9、19.1.9、19.0.10、18.11.13(10 月 21 日) |
GitLab 表示 GitLab.com 已執行修補版本,GitLab Dedicated 租戶若執行 19.3.5-internal0 或 19.4.2-internal0 以後版本亦無需動作。由於 GitLab 打包自家的 Ruby 並直接修補該副本,自架用戶安裝上述任一修補版本即完成修補。但 GitLab 的修補只涵蓋其內嵌於 /opt/gitlab/embedded 下的 Ruby,同一台主機上的系統 Ruby、其他 Ruby 應用程式與容器映像檔都需要各自升級。
除了 GitLab,其他廠商產品也可能在自己的目錄下內嵌 Ruby 直譯器,例如組態管理代理程式、日誌轉送工具與各類設備。這些內嵌的 Ruby 只有在該廠商釋出更新時才會修補,使用者應主動向廠商確認其內嵌 Ruby 是否已納入 #13332、#19020 與 #16801 三項修補。
沒有 Ruby 自己的 CVE,掃描工具看不見
Ruby 官方在公告中表示,目前「至少暫時」未為這些修補申請 Ruby 自己的 CVE 編號,並請使用者在追蹤時改以 GitLab 的四個 CVE 編號為準,例如在資安掃描工具中。這個決定讓事件從單純的執行環境修補,變成弱點管理流程的實際難題。
軟體組成分析(SCA)、容器掃描與 SBOM 工具的運作邏輯大致相同,先辨識元件與版本,再比對弱點紀錄中列出的受影響產品與版本範圍。這條鏈在此事件中斷在三個地方。
- 沒有任何 CVE 紀錄提到 Ruby。 現有的四個 CVE 都由 GitLab 以 CNA 身分發布,cve.org 紀錄中受影響產品只有 GitLab 一項。NVD 對 CVE-2026-89078、CVE-2026-93577 與 CVE-2026-97229 的分析設定也只有
cpe:2.3:a:gitlab:gitlab的 CE 與 EE 條目,CVE-2026-110264 截至 10 月 23 日仍在分析中。沒有紀錄提到 Ruby、Onigmo 或org-ruby,掃描ruby 3.4.11不會得到任何新結果。 - 借用 GitLab 編號只在人工標註時有效。 Ruby 建議以 GitLab 的 CVE 追蹤是合理的權宜之計,但掃描工具不會自動把 GitLab 的 CVE 套到 Ruby 元件上,必須有人手動建立這層對應。
- 許多映像檔從原始碼編譯 Ruby。 官方 Docker 映像檔與許多內部基礎映像檔將 Ruby 編譯進
/usr/local,而非安裝作業系統套件。掃描工具是否會把這個直譯器記錄為元件,取決於工具本身與映像檔的建置方式。
Ruby 的立場有其脈絡。Ruby 長期認為以不信任的輸入編譯正規表達式本身就不安全,在 CVE-2022-28738 的公告中已明確陳述,但當時仍經過完整評估後將 double free 視為漏洞,CVE-2024-27282 亦比照辦理。然而 Ruby 的 Regexp 文件至今只以阻斷服務(DoS)的角度討論不信任的樣式,建議設定逾時,並未清楚說明編譯不信任的樣式是否為 Ruby 支援的使用情境。Ruby 表示將討論如何修改文件、是否將此類問題視為 Ruby 的漏洞,以及是否為這些修補申請 CVE,若有申請將更新公告。
在 Ruby 做出決定之前,這些修補的實際狀態是「一次與資安相關、但沒有自己 CVE 的執行環境更新」。對於採用 ITGC、ISO 27001 等稽核框架的組織,這代表弱點管理紀錄需要額外的人工說明。可行的作法包括在每個服務的工單中記錄升級前後的 Ruby 版本與日期,以及在 SBOM 或 VEX 文件中將 GitLab 的 CVE 編號標註於 Ruby 元件並附上 Ruby 公告連結。
另一個容易被忽略的面向是 Linux 發行版。發行版的安全追蹤系統以 CVE 編號為索引,沒有 Ruby CVE 可能意味著套件更新沒有對應的追蹤條目。發行版套件也經常在不更改上游版本號的情況下回溯修補,因此應以套件變更紀錄是否提及三個 PR 編號為準,而非只看版本字串。
檢測與因應建議
順序很重要。升級執行環境可以一次關閉三項漏洞,涵蓋包括藏在 gem 內的所有程式碼路徑。程式碼稽核則用來判斷每個服務升級的急迫程度,以及暫時無法升級的服務該如何處置。
兩個看似有效、實際無效的緩解措施
Ruby 公告直接點名兩個開發者最先想到的方法都無法防護這些漏洞。
Regexp.timeout無效。 逾時機制限制的是比對執行的時間,而記憶體損壞發生在樣式編譯階段,repeat ID 溢位的越界寫入也不是逾時能攔截的。- 限制樣式長度不可靠。 Ruby 表示部分漏洞可由很短的樣式觸發,一個簡短的重複量詞就能展開成極大的編譯後程式。
Ruby 提供的唯一替代方案是,若無法升級,就停止以不信任的輸入編譯正規表達式。
盤點每一個 Ruby 執行環境
從實際執行中的環境開始,而非從程式碼儲存庫的設定開始。在主機或容器內以 ruby -e 'puts RUBY_DESCRIPTION' 與 which -a ruby 確認版本與路徑,並以 find / -xdev -type f -name ruby -perm -u+x 找出廠商產品內嵌的直譯器。對 Kubernetes 叢集,先列出所有使用中的映像檔,再逐一檢查其 Ruby 版本。在程式碼儲存庫中,則需檢查 Dockerfile 的 FROM 行、.ruby-version、.tool-versions、Gemfile 的 ruby 指令與 Gemfile.lock 的 RUBY VERSION 區段。
以下檢查腳本可在任何安裝了 Ruby 的環境執行,依版本號判斷該直譯器是否已含全部三項修補。
fixed = { "4.0" => "4.0.9", "3.4" => "3.4.12", "3.3" => "3.3.13" }
series = RUBY_VERSION.split(".")[0, 2].join(".")
current = Gem::Version.new(RUBY_VERSION)
if fixed.key?(series)
ok = current >= Gem::Version.new(fixed[series])
puts "#{RUBY_VERSION}: #{ok ? '已含 Onigmo 修補' : '請升級至 ' + fixed[series]}"
else
puts "#{RUBY_VERSION}: 此系列無修補,請遷移至 3.4 或 4.0"
end發行版套件若已回溯修補,版本號會低於上述數字,此時應以發行版的變更紀錄為準。
升級方式
| 安裝方式 | 作法 |
|---|---|
| Docker 映像檔 | 更新基礎映像檔標籤(例如 FROM ruby:3.4.12-slim)並重新建置,而非修補執行中的容器。官方 ruby 映像檔的 4.0.9、3.4.12、3.3.13 標籤已於 10 月 23 日約 17:00 UTC 上架 |
| rbenv 或 asdf | 先更新 ruby-build 定義,再執行 rbenv install 3.4.12 或 asdf install ruby 3.4.12 |
| 原始碼編譯 | 下載 tarball 後比對 ruby-lang.org 公布的 SHA256 再建置 |
| Bundler 專案 | 同步更新 .ruby-version 與 Gemfile 的 ruby 指令,執行 bundle update --ruby 後跑完整測試 |
| Ruby 3.2 或更早 | 無修補版本,需規劃遷移至 3.4 或 4.0,過渡期間對每個服務套用程式碼層級的控制 |
| 廠商產品內嵌 Ruby | 升級產品本身,並向廠商確認其內嵌 Ruby 是否含三項修補 |
升級後應再次對執行中的環境確認版本,而非只確認儲存庫。未重啟的容器、固定在舊映像檔的工作程序集區,以及拉取過期標籤的排程工作,都是常見的漏網之魚。對長時間執行的程序而言,真正有效的是程序啟動時載入的直譯器版本。
稽核程式碼中的風險呼叫
以 rg -n --type ruby 'Regexp\.(new|compile|union)\s*\(' 搜尋明確的建構子呼叫,以 rg -n --type ruby '/[^/\n]*#\{[^}]*\}[^/\n]*/[imxo]*' 搜尋內插進正規表達式字面值的情況。對每一個命中的位置回答三個問題。攻擊者能否直接或透過已儲存的資料(例如儲存的篩選條件、租戶設定)影響該字串。該字串在進入樣式前是否經過 Regexp.escape。該功能是否真的需要正規表達式語法,或是用純子字串、前綴比對即可。
GitLab 的無需登入路徑經由 gem 觸發,因此也要以 bundle list --paths 搭配同樣的搜尋檢查已安裝的相依套件。多數 gem 是從自身常數建構樣式,重點放在會解析攻擊者可提供內容的 gem,例如標記與文件渲染器、模板引擎、搜尋與查詢解析器、路由輔助工具,以及讀取使用者設定的套件。
若暫時無法升級,可降低曝險的程式碼層級控制包括,對需要字面比對的輸入使用 Regexp.new(Regexp.escape(input)),在產品允許的情況下以子字串、前綴或 glob 比對取代使用者自訂的正規表達式,以及優先移除無需登入介面上的正規表達式輸入。GitLab 的兩個 CVSS 10.0 漏洞都是無需登入的路徑。
利用 GitLab 發布的偵測規則
GitLab 發布了 Sigma 規則「GitLab org-ruby or Onigmo native worker crash」(ID 58461d1f-2ad8-41a8-861e-57c8b697657f,狀態為實驗性,等級為 critical),當 Ruby 原生 [BUG] 當機報告中同時出現 org-ruby 檔案或 Onigmo 堆疊框架時觸發。規則比對的字串對任何 Ruby 服務都適用,編譯階段的當機會出現 onig_compile、compile_tree、compile_length_tree,比對階段的越界寫入則出現 match_at、onig_search_gpos、rb_reg_search_set_match、rb_str_sub_bang。
GitLab 明確表示這只是佐證訊號。成功的攻擊是安靜的,不一定會造成當機,因此沒有當機紀錄不代表實例未遭入侵。GitLab 也指出架構、訊號名稱與記憶體位址會因環境而異,其實驗室在 arm64-darwin 上看到的是 Bus Error,正式環境的 x86_64-linux 通常是 Segmentation fault,因此應以 Onigmo 的框架名稱為比對依據。
要讓這個訊號存在,應用程式伺服器與背景工作程式的標準錯誤輸出必須有被送進日誌平台。GitLab 指出原生當機報告通常預設不會被收集。GitLab Omnibus 的 Puma 日誌位於 /var/log/gitlab/puma/puma_stderr.log,Sidekiq 位於 /var/log/gitlab/sidekiq/current。GitLab 未公布 IP 位址、網域等網路指標,目前也沒有惡意檔案雜湊可供比對。
截至 10 月 23 日的狀態。 四個 CVE 均未列入 CISA KEV 目錄(版本 2026.10.23)。CISA ADP 對四筆紀錄的補充評估將「遭利用狀況」標為 none、「技術衝擊」標為 total,並將 CVE-2026-97229 與 CVE-2026-110264 標為可自動化利用。GitLab 公告未提及是否已觀察到野外攻擊。
延伸閱讀
Ruby 4.0.9 Released、Ruby 3.4.12 Released、Ruby 3.3.13 Released、Ruby 4.0.8 Released
ruby/ruby PR #13332、PR #19020、PR #16801
GitLab Critical Patch Release 19.5.1, 19.4.3, 19.3.6, 19.2.9, 19.1.9, 19.0.10, 18.11.13
GitLab Security 偵測規則:Onigmo native worker crash
The Hacker News: Ruby Fixes Three Regex Engine Bugs Responsible for Four Critical GitLab RCE Flaws
Safeguard: Ruby 4.0.9, 3.4.12, 3.3.13: Onigmo fixes behind four GitLab RCE CVEs








