Supabase MCP Server鑒權(quán)加固指南:解決租戶隔離與權(quán)限校驗漏洞

MCP生態(tài)實戰(zhàn)價值:Supabase漏洞反思與MCP Server鑒權(quán)加固
Supabase漏洞事件復(fù)盤
Hacker News上一篇關(guān)于“Supabase MCP漏洞可導(dǎo)出全庫SQL”的討論,實際指向一個更具體的問題:Supabase的MCP服務(wù)端未對/mcp/export端點做租戶隔離和操作權(quán)限校驗。攻擊者只需構(gòu)造一個合法認(rèn)證頭,就能觸發(fā)全庫導(dǎo)出,拿到所有表結(jié)構(gòu)和數(shù)據(jù)。
這不是MCP協(xié)議本身的漏洞,而是Supabase在實現(xiàn)MCP Server時跳過了三個關(guān)鍵環(huán)節(jié):
- 沒綁定請求上下文到用戶身份(例如沒提取JWT中的
tenant_id或role字段) - 沒校驗操作語義(
export屬于高危動作,不應(yīng)開放給普通用戶) - 沒限制導(dǎo)出范圍(默認(rèn)導(dǎo)出全部schema,未強(qiáng)制指定
--schema=app_public之類約束)
這個案例暴露的不是協(xié)議缺陷,而是MCP Server開發(fā)中常見的“協(xié)議搬運(yùn)工”陷阱:照搬協(xié)議定義,卻忽略業(yè)務(wù)語義層的訪問控制。
MCP Server必須守住的三道防線
MCP協(xié)議本身不規(guī)定鑒權(quán)模型,它只定義消息格式和路由規(guī)則。Server實現(xiàn)者必須自己補(bǔ)上這三層防護(hù):
1. 上下文綁定:把請求錨定到真實身份
MCP請求里帶Authorization頭,但不能只驗證token是否有效。要解析出sub、tenant_id、scope等聲明,并在后續(xù)所有處理中顯式傳遞。
# 正確做法:解析并注入上下文
from flask import g, request
import jwt
def auth_middleware():
token = request.headers.get('Authorization', '').replace('Bearer ', '')
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
g.user_id = payload['sub']
g.tenant_id = payload['tenant_id']
g.scopes = set(payload.get('scope', '').split())
except Exception:
return {'error': 'Invalid token'}, 401
@app.before_request
def before_request():
return auth_middleware()2. 動作分級:按操作敏感度動態(tài)授權(quán)
MCP操作不能一刀切。list_tools可以公開,export_db必須要求admin:tenant scope,delete_tool則需額外校驗工具歸屬。
def require_scope(*required_scopes):
def decorator(f):
def wrapped(*args, **kwargs):
missing = set(required_scopes) - g.scopes
if missing:
return {'error': f'Missing scopes: {missing}'}, 403
# 額外檢查:export_db必須限定schema
if 'export_db' in required_scopes and request.args.get('schema') != g.tenant_id:
return {'error': 'Export schema must match tenant'}, 403
return f(*args, **kwargs)
return wrapped
return decorator
@app.route('/mcp/export', methods=['POST'])
@require_scope('export_db')
def export_db():
# 此時g.tenant_id和scopes均已驗證
return run_export(g.tenant_id)3. 數(shù)據(jù)沙箱:用數(shù)據(jù)庫能力做物理隔離
別靠應(yīng)用層if-else過濾數(shù)據(jù)。用PostgreSQL的Row Level Security(RLS)或MySQL的Schema級權(quán)限,讓數(shù)據(jù)庫自己攔住越權(quán)查詢。
-- PostgreSQL RLS策略示例
ALTER TABLE public.tools ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON public.tools
USING (tenant_id = current_setting('app.current_tenant', true)::uuid);然后在應(yīng)用層設(shè)置會話變量:
@app.before_request
def set_tenant_context():
db.execute("SET app.current_tenant = %s", (g.tenant_id,))這樣即使代碼漏掉某處校驗,數(shù)據(jù)庫也會拒絕返回其他租戶的數(shù)據(jù)。
鑒權(quán)加固不是加功能,是改流程
很多團(tuán)隊把鑒權(quán)當(dāng)成“加個中間件”,結(jié)果變成層層嵌套的裝飾器。真正有效的加固,是把權(quán)限判斷嵌入到核心路徑里:
- 工具注冊時,記錄
owner_tenant_id - 請求解析后,立即查
tools表確認(rèn)該工具屬于當(dāng)前租戶 - 執(zhí)行前,用
pg_advisory_xact_lock()對租戶ID加事務(wù)鎖,防并發(fā)沖突 - 日志里強(qiáng)制記錄
tenant_id、user_id、action、duration_ms
@app.route('/mcp/execute', methods=['POST'])
def execute_tool():
tool_name = request.json['tool']
# 1. 查工具歸屬
tool = db.fetch_one("SELECT * FROM tools WHERE name = %s AND tenant_id = %s",
(tool_name, g.tenant_id))
if not tool:
return {'error': 'Tool not found or access denied'}, 404
# 2. 加租戶鎖(防并發(fā)修改配置)
db.execute("SELECT pg_advisory_xact_lock(hashtext(%s))", (g.tenant_id,))
# 3. 執(zhí)行并計時
start = time.time()
result = run_tool(tool, request.json['args'])
duration = int((time.time() - start) * 1000)
# 4. 記錄審計日志(含租戶上下文)
audit_log.insert({
'tenant_id': g.tenant_id,
'user_id': g.user_id,
'action': 'execute_tool',
'tool': tool_name,
'duration_ms': duration,
'status': 'success' if result else 'failed'
})
return result商業(yè)化落地的關(guān)鍵:把安全設(shè)計變成客戶能感知的價值
金融客戶不關(guān)心你用了RBAC還是ABAC,但他們清楚知道:“如果你們的MCP Server被攻破,我的交易數(shù)據(jù)會不會流到競爭對手那里?”
所以商業(yè)化路徑要倒過來推:
1. 從合規(guī)需求反推技術(shù)方案
- GDPR → 要求數(shù)據(jù)不出境 → 在MCP Server里加
region字段,路由時強(qiáng)制匹配 - HIPAA → 要求審計日志留存6年 → 把
audit_log表設(shè)為分區(qū)表,按月自動歸檔 - 等保三級 → 要求雙因素登錄 → 在token簽發(fā)前,校驗TOTP code
2. 把防護(hù)能力包裝成可售特性
| 客戶痛點 | 技術(shù)實現(xiàn) | 產(chǎn)品話術(shù) |
|---|---|---|
| 怕多租戶數(shù)據(jù)混雜 | RLS + tenant_id強(qiáng)綁定 | “物理級租戶隔離,通過PG官方RLS策略保障” |
| 怕誤刪生產(chǎn)數(shù)據(jù) | DELETE操作需二次確認(rèn)+72小時回收站 | “企業(yè)級操作保險箱,刪除自動進(jìn)回收站” |
| 怕API密鑰泄露 | token支持細(xì)粒度scope + 1小時過期 | “最小權(quán)限令牌,支持按工具、按環(huán)境分發(fā)” |
3. 用客戶自己的環(huán)境驗證效果
別只講PPT。給POC客戶部署一個帶審計看板的實例:
- 實時顯示“今日攔截越權(quán)請求:17次”
- 展示一條被拒請求的完整鏈路:
JWT解析→scope校驗失敗→拒絕→寫入審計日志 - 提供一鍵生成合規(guī)報告的按鈕(含GDPR/HIPAA條款映射)
真實項目踩坑記錄
我們給一家跨境支付公司上線MCP Server時,在/mcp/transfer接口栽過跟頭:
- 初始版本只校驗了
transfer_fundsscope,沒校驗from_account是否屬于當(dāng)前租戶 - 攻擊者用自己租戶的token,把
from_account改成另一家客戶的賬戶ID,成功轉(zhuǎn)出資金 修復(fù)方案不是加if判斷,而是:
- 在數(shù)據(jù)庫加外鍵約束:
transfers.from_account_id → accounts.id+accounts.tenant_id = transfers.tenant_id - 查詢時強(qiáng)制JOIN:
SELECT * FROM transfers t JOIN accounts a ON t.from_account_id = a.id AND a.tenant_id = %s - 所有account ID參數(shù),統(tǒng)一走
uuid類型校驗,防SQL注入
- 在數(shù)據(jù)庫加外鍵約束:
這次事故讓我們確認(rèn)一條原則:鑒權(quán)邏輯必須下沉到數(shù)據(jù)訪問層,應(yīng)用層只做快速失?。╢ast fail),不承擔(dān)最終裁決責(zé)任。
下一步行動清單
- [ ] 檢查現(xiàn)有MCP Server所有端點,標(biāo)記出
export、delete、exec類高危動作 - [ ] 給每個高危端點補(bǔ)上
tenant_id顯式校驗,刪掉所有“全局查詢”SQL - [ ] 在數(shù)據(jù)庫啟用RLS或?qū)?yīng)隔離機(jī)制,用
EXPLAIN ANALYZE驗證策略生效 - [ ] 把審計日志字段從
user_id擴(kuò)展為{tenant_id,user_id,ip,ua},接入SIEM系統(tǒng) - [ ] 更新客戶文檔,在“安全特性”章節(jié)直接寫明:“所有導(dǎo)出操作強(qiáng)制限定schema,無法跨租戶”