本機回送不是信任邊界:CVE-2026-2611
2026年5月19日,National Vulnerability Database 發布 CVE-2026-2611。MLflow 3.9.0 的 Assistant 功能在本地 /ajax-api 端點上存在來源驗證不當的問題。惡意網頁只要在任何分頁中載入,就能向 Assistant 的本地伺服器發出跨來源請求,修改其設定以啟用完整存取模式,接著透過內建的 Claude Code CLI 整合執行任意命令。Huntr 給此漏洞的 CVSS 3.0 評分為 9.6(CRITICAL),向量為 AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H:可透過網路觸及、複雜度低、不需權限、需要使用者互動、範圍變更,且對機密性/完整性/可用性皆造成高衝擊。1
根本原因並不新奇,修補方式也不特殊,但威脅模型變了。任何繫結到本機回送位址,並把可變更狀態的端點暴露給瀏覽器分頁的本地服務,都是信任邊界表面。當該服務驅動的 AI 代理能讀取檔案、執行 shell 命令、推送程式碼時,「只在本機回送上開放」就不再是有效的安全控制。隨著更多開發者工具把 LLM 代理包進本地 Web UI,CVE-2026-2611 正是這整類漏洞的標準案例,而且這類問題會一再出現。
TL;DR
CVE-2026-2611 記錄的是 MLflow 3.9.0 中 CORS 路徑列舉不完整的漏洞。Assistant 功能暴露了 /ajax-api/ 端點,但 MLflow 的 is_api_endpoint() 檢查沒有涵蓋它們,因此封鎖 CORS 的中介軟體跳過了這些路徑。既有的 CORSMiddleware 又允許 allow_origins=["*"],於是來自 evil.com 的跨來源請求可以呼叫 /ajax-api/3.0/mlflow/assistant/config,把 Assistant 切到完整存取模式,再透過內建的 Claude Code CLI 整合送出 shell 命令。1上游修補(PR #20832,”Block CORS for ajax paths”)已在 MLflow 3.10.0 加入:將 /ajax-api/ 納入受保護路徑列舉,並把 allow_origins 收緊為已設定的允許清單加上 localhost 正規表示式。2下方防禦措施針對的是本地 AI 代理這整個類別,而不只是 MLflow。
重點摘要
| 角色 | 行動 |
|---|---|
| 執行 MLflow 的 AI/ML 工程師 | 升級到 MLflow 3.10.0;稽核其他任何監聽本機回送位址的本地 AI 代理 UI |
| 安全團隊 | 將「本地 LLM 代理回送服務」加入威脅模型範本;在這一類工具中,一個 CORS 缺口就等於 RCE |
| 建置本地代理 UI 的工具作者 | 在單一位置列舉每個 API 路徑前綴;即使伺服器只繫結 localhost,也禁止 allow_origins=["*"];破壞性端點應以新的使用者動作把關,而不只是檢查來源 |
| 工程主管 | 將團隊的 AI 程式碼代理放進沙箱,並把其命令執行能力視為實際影響範圍 |
攻擊鏈長什麼樣子
這條鏈只有5步。每一步使用的都是被授權的能力;失敗的是它們組合後的邊界。
| 步驟 | 動作 | 攻擊者控制什麼 |
|---|---|---|
| 1 | 使用者在本機執行 mlflow ui;Assistant 監聽本機回送位址(預設 127.0.0.1) |
尚未控制任何東西(本地服務) |
| 2 | 使用者在 MLflow 執行期間開啟 evil.com 的任一分頁 |
頁面內容 |
| 3 | evil.com 發出 fetch('http://127.0.0.1:5000/ajax-api/3.0/mlflow/assistant/config', { method: 'PUT', credentials: 'include', body: ... }) |
跨來源請求 |
| 4 | MLflow 接受請求,因為 /ajax-api/ 不在 CORS 封鎖路徑列舉中;Assistant 設定被切換為完整存取 |
Assistant 的執行環境設定 |
| 5 | evil.com 透過 Assistant 的聊天端點送出命令酬載;MLflow 呼叫本地 Claude Code CLI,並繞過權限檢查;命令以使用者身分執行 |
任意程式碼執行 |
攻擊者不需要在受害者機器上安裝任何東西。只要受害者在 mlflow ui 執行時,載入一個含有酬載的分頁即可。攻擊可從任何來源發動,因為既有 CORSMiddleware 設定了 allow_origins=["*"],而路徑層級的 CORS 封鎖中介軟體沒有涵蓋 /ajax-api/ 前綴。2
Claude Code 整合是權限放大器。MLflow 的 ClaudeCodeProvider 會透過 shutil.which("claude") 尋找本地 claude CLI;若不存在,則指示使用者用 npm install -g @anthropic-ai/claude-code 安裝。當 Assistant 的 permissions.full_access 設定旗標開啟時,provider 會把 CLI 呼叫延伸為 --permission-mode bypassPermissions,也就是 Claude Code 用來關閉互動式權限提示、讓工具呼叫不經使用者確認即可執行的模式。3代理原本被授權的能力(shell、檔案寫入、HTTP)變成帶自然語言 UX 的遠端 shell,這正是攻擊者想要的結果。
根本原因是路徑列舉不完整
讀修補 commit 後,問題一目了然。MLflow 的 security_utils.py 原本有一個常數:
API_PATH_PREFIX = "/api/"
以及一個檢查:
def is_api_endpoint(path: str) -> bool:
return path.startswith(API_PATH_PREFIX) and path not in TEST_ENDPOINTS
CORS 封鎖中介軟體呼叫 is_api_endpoint(),用來決定是否套用來源驗證。任何不是以 /api/ 開頭的路徑,都會被當成非 API 路徑放行。Assistant 為其面向瀏覽器的端點引入了 /ajax-api/。但沒有人更新 is_api_endpoint(),把新前綴納入檢查。
修補是在 security_utils.py 中的單行行為變更:2
API_PATH_PREFIX = "/api/"
AJAX_API_PATH_PREFIX = "/ajax-api/"
def is_api_endpoint(path: str) -> bool:
return (
path.startswith(API_PATH_PREFIX) or path.startswith(AJAX_API_PATH_PREFIX)
) and path not in TEST_ENDPOINTS
此外,fastapi_security.py 也有對應修補:將既有 CORSMiddleware 上的 allow_origins=["*"],替換為已設定的允許清單加上 localhost 正規表示式。整體變更在 security_utils.py 是新增5行、刪除2行,在 fastapi_security.py 是新增5行、刪除1行。2
這就是整個漏洞。CVE-2026-2611 之所以發生,是因為新端點前綴出貨時,沒有人更新那個負責列舉哪些路徑要套用 CORS 保護的單一函式。攻擊面來自一個被遺忘的設定常數。
為什麼這個模式會重演
這個漏洞模式比 MLflow 更早,也比 AI 代理更早。只要有路徑列舉,Web 框架就可能出現路徑列舉錯誤。改變的是後果。過去,本地 Web UI 忘了列舉某個端點,可能只是洩漏一些設定資料,或觸發阻斷服務。現在,本地 Web UI 在執行具備 shell 存取權的 AI 程式碼代理時忘了列舉一個端點,洩漏的就是整台開發者機器。
本地 AI 代理 UI 正在成為一個快速成長的類別。任何把 LLM 代理包進由 127.0.0.1 提供服務的瀏覽器 UI 中的工具,都落在範圍內:帶有聊天面板的實驗追蹤器(MLflow 這種型態)、透過 localhost API 驅動代理的 IDE 外掛、擴充套件透過 HTTP 溝通的開發模式伺服器、本地模型執行器的 Web 儀表板。它們都繫結到本機回送位址。它們都把可變更狀態的端點暴露給瀏覽器分頁。它們都執行有權呼叫 shell、寫入檔案、推送到 Git 的代理。每一個都只差一個路徑列舉錯誤,就會變成 CVE-2026-2611。
這個類別底下的共同假設是:「瀏覽器的同源政策會保護本機回送伺服器,因為合法請求也來自 127.0.0.1。」只要任何跨來源關口(CORS、Host 標頭驗證、Origin 標頭驗證、Fetch Metadata 標頭)漏掉某個端點前綴,這個假設就會失效。當伺服器明確接受請求時,瀏覽器不會保護伺服器。
我在3月討論過的靜默外送攻擊面,是同一個信任假設在相反方向失效。靜默外送把出口藏在代理被授權的工具裡。本機回送誤分類則把入口藏在瀏覽器被授權的網路呼叫裡。兩者歸根究柢都是:只要任一邊界檢查失效,LLM 代理的授權能力就會變成攻擊原語。
這個類別實際包含什麼
此漏洞類別有4個要件。4個條件同時成立時,服務就可被利用:
- 本地 Web 伺服器繫結到本機回送位址或
0.0.0.0。只在本機回送上開放是較常見的變體。保護 localhost 服務免受跨來源分頁攻擊的是瀏覽器的 CORS、Host 與 Origin 檢查,而不是網路繫結本身。 - 可變更狀態的端點不需要使用者動作即可觸及。例如設定變更、命令提交、檔案寫入。若任意來源的
fetch()可以改變狀態,該服務就可被觸及。 - 本地使用者擁有、但網頁不該擁有的能力。讀取家目錄、執行 shell 命令、使用已儲存憑證呼叫雲端 API、修改原始碼檔案。
- 少了一個邊界檢查。被遺忘的路徑前綴、萬用字元
allow_origins、缺少 Host 標頭驗證、缺少 Origin 驗證、缺少 Sec-Fetch-Site 強制檢查。
CVE-2026-2611 四項全中。這次修補針對 MLflow 的 /ajax-api/ 前綴關閉了第4項。第1到第3項仍存在於其他每一個本地 AI 代理工具中。這一類的下一個 CVE 看起來會完全相同,只是套件名稱不同。
防禦本地 AI 代理回送漏洞類別
標準 CORS 建議(將 allow_origins 設為特定清單,只在需要時啟用 credentials)是必要的,但不足夠。這個類別需要更深一層的控制。
在單一位置列舉路徑前綴,並撰寫測試,讓任何未受保護的新前綴都會失敗。MLflow 的漏洞之所以進入產品,是因為團隊在 /api/ 出貨時更新了 is_api_endpoint(),卻在 /ajax-api/ 出貨時忘了同樣處理。一個測試若能迭代 FastAPI app 上每個已註冊的路由,詢問 is_api_endpoint() 該路由是否受保護,並在任何無法辨識的路徑前綴上失敗,就能在 PR 階段抓到這個問題。這項檢查大約只需要20行測試程式碼。
驗證 Origin 與 Host 標頭,而不只依賴 CORS。CORS 保護瀏覽器不讀取伺服器回應;它不保護伺服器免於接受請求。本地 AI 代理服務應拒絕 Origin 標頭不在明確允許清單中的請求,也應拒絕 Host 標頭不是 127.0.0.1 或 localhost 的請求(後者用來防止 DNS rebinding 攻擊)。兩項檢查都應預設拒絕。
以 Sec-Fetch-Site: same-origin 替破壞性端點把關。瀏覽器在任何跨來源 fetch() 上都會送出 Sec-Fetch-Site: cross-site,包括從 evil.com 到 127.0.0.1。Fetch Metadata Request Headers 規格定義了這個標頭的值,Chrome、Firefox、Safari 與 Edge 也都會送出。4在可變更狀態的端點上,加入拒絕非 Sec-Fetch-Site: same-origin 請求的中介軟體,可以提供額外防線,而且不依賴人工記住每個路徑前綴。
將 AI 代理的執行能力放進沙箱。Claude Code、Cursor、Aider 與 Continue 預設都以使用者完整權限執行,因為這是最簡單的 UX。以工作區為單位建立能力沙箱(Linux 上用 bwrap 搭配明確掛載規則;以 .app 套裝出貨的工具在 macOS 上使用 Apple 的 App 沙箱;跨平台則為每個工作區建立容器),可以在不犧牲主要工作流程的情況下降低影響範圍。這項緩解會增加摩擦。摩擦正是重點。
在審查前,把代理的 commit 視為不可信。分支保護、必要審查、簽署 commit。「AI 代理具備 commit 存取權」的模型,在攻擊者攻陷代理本地環境的那一刻就瓦解了。我在 AI 程式碼審查需要異議談過的審查者異議,在這裡同樣適用。如果 AI 代理與人類審查者永遠同意彼此,那人類審查者只是在當裝飾。
這個類別的下一個 CVE 可能長什麼樣子
真正有意思的問題,不是下一個 CVE-2026-2611 形狀的漏洞會不會出現,而是哪個套件會先出貨。候選者散布在本地 AI 工具生態系:把代理包進 UI 的實驗追蹤器、透過 HTTP 與本地代理後端溝通的 IDE 外掛、任何工具在會話中開啟的開發模式伺服器、擴充套件用來驅動代理 UI 的 localhost API。這些工具目前尚未發布這種型態的 CVE。但其中已有不少程式碼路徑會繫結到本機回送位址,並以瀏覽器作為信任關口接受跨來源請求。
這個類別比 CVE-2026-2611 更大,而且威脅模型已經轉移。2026年的開發者機器上,會有能呼叫 shell、寫入檔案、推送 commit、呼叫雲端 API 的 AI 代理。驅動這些代理的本地 Web UI,會透過其 HTTP 伺服器執行的信任邊界,繼承代理的權限。2020年 Web 框架上的 CORS 設定錯誤,可能洩漏一些設定資料。2026年本地 AI 代理 UI 上的 CORS 設定錯誤,洩漏的是整台開發者機器,以及部署到正式環境的能力。
修補不是一個 patch,而是一組控制:明確路徑列舉與測試、縱深防禦的標頭驗證、能力沙箱,以及對代理產生 commit 的審查紀律。只出貨其中一項、卻缺少其他控制的工具,等於用不同前綴出貨 CVE-2026-2611 的繼任者。
FAQ
什麼是 CVE-2026-2611?
CVE-2026-2611 是 National Vulnerability Database 給 MLflow 3.9.0 Assistant 功能中「來源驗證不當」缺陷的識別碼。此缺陷讓任何網頁都能對 Assistant 的本地 /ajax-api 端點發出跨來源請求,修改其設定以啟用完整存取模式,接著透過內建的 Claude Code CLI 整合執行任意命令。CVSS 3.0 基礎分數為 9.6(CRITICAL)。已在 MLflow 3.10.0 透過 PR #20832(”Block CORS for ajax paths”)修復。12
根本原因是什麼?
MLflow 的 is_api_endpoint() 檢查只列舉了 /api/ 路徑前綴。Assistant 功能引入 /ajax-api/ 端點時,沒有人更新列舉。CORS 封鎖中介軟體因此讓前往 /ajax-api/ 的跨來源請求在沒有任何來源檢查的情況下通過。第二個弱點又加劇了這個漏洞:既有 CORSMiddleware 允許 allow_origins=["*"],因此即使未被封鎖的路徑,也會向任何來源回傳寬鬆的 CORS 標頭。修補加入 AJAX_API_PATH_PREFIX = "/ajax-api/" 到路徑檢查,並把 allow_origins=["*"] 替換為已設定的允許清單加上 localhost 正規表示式。2
我如何知道自己的 MLflow 安裝是否受影響?
執行 pip show mlflow | grep Version。依 NVD 的 CPE 範圍,MLflow 版本 >=3.9.0,<3.10.0 受影響。MLflow 3.10.0(2026年2月20日發布)已包含修補。請用 pip install --upgrade mlflow 升級,並重新啟動任何正在執行的 mlflow ui 程序。
沒有安裝 Claude Code CLI 時,攻擊仍會成功嗎?
設定修改步驟仍會成功。任意程式碼執行步驟則需要受害者的 PATH 上存在 Claude Code CLI,因為 MLflow 的 Assistant 會透過 shutil.which("claude") 呼叫它。若沒有 CLI,最壞情況會從 RCE 降為資訊洩漏與任意設定竄改。對已設定 Claude Code 後端的使用者(也就是文件記載的 Assistant 設定路徑)而言,實務暴露風險就是 RCE。
我該如何保護其他本地 AI 代理服務?
稽核4個要件:本機回送或萬用繫結、可變更狀態的端點、代理層級能力,以及任何可能有缺口的單一邊界檢查。加入明確的 Origin 允許清單、針對 127.0.0.1/localhost 的 Host 標頭驗證(用來抵禦 DNS rebinding),並在破壞性端點上以 Sec-Fetch-Site: same-origin 把關。使用 bwrap、Apple 的 App 沙箱或每個工作區一個容器,將代理的執行能力沙箱化。在審查前,把代理產生的 Git commit 視為不可信。
-
National Vulnerability Database。”CVE-2026-2611: MLflow Assistant improper origin validation.”發布於2026年5月19日。CVSS 3.0 基礎分數 9.6(CRITICAL),向量
CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H,由 Huntr 核發。修補已在 MLflow 3.10.0 出貨。https://nvd.nist.gov/vuln/detail/CVE-2026-2611。原始報告:https://huntr.com/bounties/8462addd-b464-4a84-b6a2-5529604e6e5a。 ↩↩↩ -
MLflow。”Block CORS for ajax paths (#20832).”由 Tomu Hirata 於2026年2月16日合併。變更檔案:
mlflow/server/fastapi_security.py(+5/-1)、mlflow/server/security_utils.py(+5/-2),另有測試更新。此 diff 將is_api_endpoint()的單一前綴檢查替換為雙前綴檢查,並把既有CORSMiddleware的allow_origins從["*"]收緊為已設定的允許清單加上 localhost 正規表示式。https://github.com/mlflow/mlflow/commit/8f9c8a53af90842944101eb8b7d60706822c81bc。 ↩↩↩↩↩↩ -
MLflow 3.10.0 原始碼。
mlflow/assistant/providers/claude_code.py:shutil.which("claude")查找(第268、280、355行);安裝提示"Install it with: npm install -g @anthropic-ai/claude-code"(第286行);由config.permissions.full_access把關的 permission-mode 升級cmd.extend(["--permission-mode", "bypassPermissions"])(第379–381行)。mlflow/assistant/config.py:PermissionsConfig.full_access: bool = False預設值(第15行)。mlflow/server/assistant/api.py:router 前綴/ajax-api/3.0/mlflow/assistant(第63行);GET /config讀取端點(第261行);PUT /config變更端點(第276行),跨來源攻擊的目標正是這個 PUT 端點,用來切換full_access。已依 PyPI 上的 MLflow 3.10.0 sdist(mlflow-3.10.0.tar.gz)驗證。 ↩ -
W3C。”Fetch Metadata Request Headers.”定義
Sec-Fetch-Site及其值cross-site、same-origin、same-site、none。Chrome 76+、Firefox 90+、Safari 16.4+ 與 Edge 79+ 皆已實作。https://www.w3.org/TR/fetch-metadata/。 ↩