🔐 MCP 對象級授權實驗室
一個小巧、可自託管、設計上即存在漏洞的 MCP 服務器,用於學習 對象級 / 跨租戶授權 漏洞(BOLA / IDOR —— CWE-639、CWE-862)如何出現在模型上下文協議(MCP)工具中,以及如何狩獵它們。
它是一個多租戶筆記服務器,暴露六個 MCP 工具。其中五個的授權是正確的。恰好有一個缺少訪問檢查,因此一個組織中的調用者可以通過它訪問另一個組織的數據。這個漏洞是被刻意設計得十分隱蔽的:出錯的工具看起來和它的同類一模一樣——只是少了一行代碼。找到它,在本地利用它,然後撥動一個開關,就能看到它被修復。
📖 為什麼存在這個實驗室
大多數 MCP 安全關注點都集中在 提示注入(prompt injection) 與 工具投毒(tool-poisoning) 上。而對象級授權是一個更安靜、且完全不同的類別,常規的提示注入測試套件和掃描器根本發現不了它。
當 MCP 服務器是多租戶時,每一個從客戶端提供的 id 解析對象的工具,都必須驗證調用者是否有權觸碰那個對象。只要有一個工具漏掉了這項檢查,你就會有跨租戶的讀取、寫入或刪除——無論提示注入防禦做得多好。要捕獲它,只能逐個閱讀每個工具上的授權邏輯,而這正是本實驗室所訓練的能力。
🚀 快速開始(< 5 分鐘)
環境要求:Node.js ≥ 20。
npm install
npm run poc
預期輸出:
MCP object-level authorization lab — two-way gate
BUILD ACTION OUTCOME EXPECT OK
vuln note_get cross-tenant (Bob → Acme) DENIED DENIED ✓
vuln note_delete cross-tenant (Bob → Acme) DELETED DELETED ✓
fixed note_delete cross-tenant (Bob → Acme) DENIED DENIED ✓
fixed note_delete same-tenant (Bob → Globex) DELETED DELETED ✓
Two-way gate: PASS (vuln exploits, fix blocks the cross-tenant delete and still allows the same-tenant one).
該 PoC 是一個真正的 MCP 客戶端。它通過 stdio 啟動服務器(全程本地——無網絡、無第三方),以 Bob(組織 Globex)身份認證,並嘗試訪問歸 Acme 所有的筆記。它運行一個雙向驗證門:
- 在 vuln 構建中,跨租戶刪除成功(即漏洞利用),而正確限定作用域的
note_get仍被拒絕——這證明服務器只有單個工具出錯,而非全局缺失檢查; - 在 fixed 構建中,同樣的跨租戶刪除被攔截,而 Bob 刪除自己組織的筆記仍然可用——這證明修復沒有過度攔截(無誤報)。
🎯 挑戰
在閱讀 src/ 之前:服務器暴露了六個筆記工具——note_list、note_get、note_create、note_update、note_delete、note_search。恰好有一個允許某個組織中的調用者影響另一個組織的筆記。是哪一個,它與其他工具的區別又在哪裡?
提示
有三個工具接受客戶端提供的 id(note_get、note_update、note_delete)。其中兩個會檢查解析出的對象是否屬於你的組織。有一個不會。
答案
note_delete。它完全像 note_get 和 note_update 一樣,從客戶端提供的 id 解析筆記,但在(LAB_MODE=vuln 時)它從未調用 requireOrgAccess(session, note)。修復就是那一行——與它的同類工具早已執行的檢查相同。參見 中 note_delete 上方的註釋塊。
🏗️ 構建方式
| 文件 | 作用 |
|---|---|
| 內存中的多租戶種子數據:組織 Acme(Alice)和 Globex(Bob),各有一些筆記。 | |
resolveSession(token) → 服務器可信的 { user, org };requireOrgAccess(session, object) —— 對象級檢查。 |
|
六個工具。note_delete 是植入的異常項。 |
|
Stdio MCP 服務器。讀取 LAB_MODE(默認 vuln / fixed)。 |
|
| 運行上文雙向驗證門的 MCP 客戶端。 |
身份模型(一處刻意的簡化)。 每個工具都接受一個 bearer token,服務器將其解析為固定的用戶與組織。調用者從不聲明自己的組織——它只出示一個 token。在生產環境的 MCP 服務器中,這一身份應來自傳輸層 / OAuth 層,而非每次調用的參數;本實驗室逐調用傳遞它,以保持單進程運行,並讓授權邏輯顯式且易於閱讀。
🔄 漏洞版 vs 修復版
只需一個環境變量即可切換植入的漏洞,因此你可以在自己的 MCP 主機中運行任意一個構建:
# 漏洞版(默認)
npm start
# 或:LAB_MODE=vuln npm start
# 修復版
LAB_MODE=fixed npm start
在 Windows PowerShell 中:
$env:LAB_MODE = 'fixed'; npm start
兩個構建之間唯一的差異,就是 note_delete 中標記為 // <-- THE FIX 的那一行。
🔍 審計清單 —— MCP 中的對象級授權
在審計一個真實的多租戶 MCP 服務器(你自己擁有的、或被授權測試的)時,可使用本清單。該漏洞類別是:"服務器認證了你是誰,卻忘了檢查你是否可以觸碰這個對象":
- [ ] 把客戶端提供的範圍當作授權依據。 某工具接受
org_id/project_id/tenant_id/account_id參數,並用它來限定查詢範圍,而不是將其與調用者的成員資格進行核對。 - [ ] 成員資格檢查與對象解析相互脫鉤。 工具驗證調用者屬於某個組織 / 項目,卻通過另一個 id(對象自身的
id)加載對象,而未確認該對象確實位於該成員資格之下。(要對你即將返回 / 變更的那個對象授權——而不是它旁邊的某個參數。) - [ ] 授權不一致——單個異常項。 大多數對象工具都有檢查;一兩個沒有。要閱讀每一個通過 id 解析對象的工具,而非抽樣。被遺忘的那個通常是較不起眼的動詞(
delete、update、archive、export,或某個 "cover"/"make-default" 之類的附帶操作)。 - [ ] 讀操作有防護,寫操作沒有。
get/list限定了作用域,但update/delete/transfer漏掉了——或反之。 - [ ] 通配符 / 哨兵值短路。 某個特殊值(
'all'、'*'、空、0、null)會完全跳過作用域過濾。 - [ ] 角色 / token 類型繞過。 "internal"、"service"、"admin" 或另一種 JWT 類型的代碼路徑跳過了逐對象檢查。
- [ ] list → get 不對稱。
list只返回你所在組織的對象,於是 id 顯得"私密"——但get/delete接受任意 id,而 id 往往可猜測或可枚舉。 - [ ] create/update 接受外部父級。
create(parent_id=…)或update上的重新掛載父級操作,接受了調用者並非成員的父級,從而把對象注入到另一個租戶。
漏洞利用原語始終相同:以租戶 B 身份認證,用屬於租戶 A 的對象 id 調用可疑工具,看是否能獲得 A 的數據(或變更它)。用本 PoC 一樣的方式確認修復——雙向:跨租戶調用必須被攔截,同時合法的同租戶調用必須仍然成功。
🛡️ 安全聲明與適用範圍
- 設計上即存在漏洞。 切勿將其部署在可訪問的網絡上,也不要將其作為真實代碼的起點。僅在本地運行、用於學習。
- 合成數據。 所有組織、用戶、筆記與 token 均為虛構。不存在真實數據、不存在真實目標,且 PoC 從不發起網絡請求——它只啟動本地服務器進程。
📄 許可證
MIT。








VIP會員