@zereight/mcp-gitlab 唯讀模式、身分驗證、傳輸防護一次被攻破
GitHub Advisory Database · 2026-09-15
讓 AI coding agent 存取 GitLab 的熱門套件 @zereight/mcp-gitlab,唯讀模式、身分驗證與本機網路防護這三道安全機制,在同一天被三張 GHSA 公告同時攻破:唯讀開關可以用一個逗號繞過,伺服端請求偽造(SSRF)會把 GitLab 權杖直接送到攻擊者主機,DNS rebinding 則讓惡意網頁遠端操控本機的 MCP 監聽埠。三個弱點分別是 GHSA-5648-rgj9-v224(無對應 CVE)、CVE-2026-61559 與 CVE-2026-61568,皆於 2026-09-15 由 GitHub Advisory Database 公開,官方分別在 v2.1.27 與 v2.1.30 修補。
漏洞機制
三張公告指向同一個設計問題:這個 MCP server 把環境變數開關當成安全邊界,卻沒有在資料真正進出的地方做驗證。它讓 LLM coding agent 透過 MCP 協定操作 GitLab,官方文件把唯讀模式、專案白名單與傳輸層驗證都列為安全機制,但三個弱點各自繞過了其中一項。
最嚴重的繞過發生在 execute_graphql 工具(GHSA-5648-rgj9-v224)。唯讀檢查用正規表示式判斷查詢裡有沒有 mutation,但偵測函式 graphqlQueryContainsWriteOperation() 只清掉註解與字串,沒處理逗號;只要把查詢寫成以逗號開頭的 ,mutation{...},GraphQL 仍會照常執行寫入,偵測卻回報唯讀。同一支處理常式也從未呼叫 getEffectiveProjectId() 或 rejectIfProjectScopedDeployment(),於是 GITLAB_ALLOWED_PROJECT_IDS 專案白名單同樣被跳過,伺服器權杖能碰到的任何專案都能被寫入。
isWrite("mutation{deleteProject(input:{id:1}){errors}}") -> DETECTED
isWrite(",mutation{deleteProject(input:{id:1}){errors}}") -> BYPASS (executes as a write under read-only mode)第二個弱點是 CVE-2026-61559 的 SSRF:當環境變數 ENABLE_DYNAMIC_API_URL=true 時,伺服器會讀取請求標頭 X-GitLab-API-URL,直接拿來當作後續 GitLab API 呼叫的位置。程式只驗證這個值是不是合法網址,不檢查主機是否可信,接著把受害者的 Private-Token 原封不動夾帶到這個攻擊者指定的網址。也就是說,任何能連上 HTTP 傳輸層的呼叫端,都能用一次請求把 GitLab 權杖偷走。
第三個弱點 CVE-2026-61568 出在 Streamable HTTP 傳輸層完全沒有 Host/Origin 檢查。伺服器預設綁定 127.0.0.1,正是 DNS rebinding 想打穿的目標:惡意網頁能讓瀏覽器把請求重新導向本機監聽埠,並帶著偽造的 Host 與 Origin 標頭,伺服器一樣照常進入 MCP 的 initialize 流程。另外兩個較輕微的問題也值得一提:未授權者能用格式合法但假造的 token 灌爆 session 上限造成阻斷服務;CI job log 會原封不動回傳給模型,等於把攻擊者可操控的內容直接餵進 agent 的上下文。
受影響版本
三個弱點都出在 npm 套件 @zereight/mcp-gitlab(對應 GitHub 專案 zereight/gitlab-mcp),受影響範圍與修補版本各不相同:
| 公告 | CVE | 受影響版本 | 修補版本 | CVSS |
|---|---|---|---|---|
| GHSA-5648-rgj9-v224 | 無 | < 2.1.30 | 2.1.30 | 8.1(High) |
| GHSA-2h44-8472-frjj | CVE-2026-61559 | >= 0.0.1, < 2.1.27 | 2.1.27 | 9.6(Critical) |
| GHSA-vmp7-252j-cwp7 | CVE-2026-61568 | < 2.1.30 | 2.1.30 | 9.6(Critical) |
受影響的不是所有使用者:以 stdio 單機執行、只給單一使用者用的部署,攻擊者本來就碰不到 HTTP 傳輸層。真正暴露的是把這個 server 當共用服務跑的團隊——開了 STREAMABLE_HTTP=true 或 SSE 傳輸、搭配 REMOTE_AUTHORIZATION=true 做多人存取,或是打開 ENABLE_DYNAMIC_API_URL=true 支援自架 GitLab 的部署。任何把 GITLAB_READ_ONLY_MODE 或 GITLAB_ALLOWED_PROJECT_IDS 當成唯一安全網、又讓 agent 能呼叫 execute_graphql 的設定,等同於完全沒有防護。
修補與緩解
官方在 v2.1.27 修掉 SSRF,在 v2.1.30 一次修掉 GraphQL 繞過與 DNS rebinding,升級到 v2.1.30 可以涵蓋全部三個弱點。公告把 DNS rebinding 的修法寫得很具體,可以直接對照原本的傳輸層設定:
// 修補前:沒有任何 Host/Origin 防護
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (id) => { streamableTransports[id] = transport; },
});
// 修補後:加上 DNS rebinding 防護與白名單
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
enableDnsRebindingProtection: true,
allowedHosts: [`127.0.0.1:${PORT}`, `localhost:${PORT}`],
allowedOrigins: [`http://127.0.0.1:${PORT}`, `http://localhost:${PORT}`],
onsessioninitialized: (id) => { streamableTransports[id] = transport; },
});針對 GraphQL 繞過,公告建議改用真正的 GraphQL parser 判斷操作類型,不要用正規表示式;execute_graphql 也應該補上跟其他工具一樣的專案範圍檢查。針對 SSRF,建議替 X-GitLab-API-URL 加上主機白名單(例如新增 GITLAB_ALLOWED_HOSTS 環境變數),拒絕不在名單內的主機;如果無法維護白名單,就該直接關閉 ENABLE_DYNAMIC_API_URL。
還沒升級的團隊,第一步是確認目前跑的版本是不是 @zereight/mcp-gitlab < 2.1.30;第二步是盤點有沒有開 STREAMABLE_HTTP/SSE 傳輸卻沒設定 SSE_AUTH_TOKEN;第三步是檢查多人環境是否還開著 ENABLE_DYNAMIC_API_URL。升級前的暫時緩解是把 execute_graphql 從工具清單移除、關閉動態 API URL、並確保傳輸層只綁定在受信任的網段。
原始來源:GHSA-5648-rgj9-v224、GHSA-2h44-8472-frjj、GHSA-vmp7-252j-cwp7