Supabase MCP插件SQL注入漏洞復盤:Server端權限校驗與工具注冊安全開發實踐
摘要:Supabase MCP插件漏洞復盤:Server端安全開發要點漏洞真實影響:不是“可能”,是已發生的數據泄露Supabase MCP插件的漏洞已在生產環境被利用。攻擊者通過構造/mcp/tools路徑下的惡意請求,繞過所有權限檢查,直接執行任意SQL查詢——包括SELECT * FROM auth.users、pg_dump導出語句等。有團隊確認其PostgreSQL日志中出現未授權的CO...

Supabase MCP插件漏洞復盤:Server端安全開發要點
漏洞真實影響:不是“可能”,是已發生的數據泄露
Supabase MCP插件的漏洞已在生產環境被利用。攻擊者通過構造/mcp/tools路徑下的惡意請求,繞過所有權限檢查,直接執行任意SQL查詢——包括SELECT * FROM auth.users、pg_dump導出語句等。有團隊確認其PostgreSQL日志中出現未授權的COPY ... TO PROGRAM調用,說明攻擊者已獲取數據庫文件系統訪問權限。
這不是理論風險。它暴露了一個關鍵事實:MCP Server實現中,工具注冊邏輯與權限校驗完全解耦。
問題根源:兩處硬傷
1. 工具調用零校驗
MCP規范要求Server對每個工具調用做三重檢查:調用方身份、工具白名單、參數合法性。但Supabase插件只做了第一項(JWT解析),且未驗證token中聲明的scope是否包含該工具權限。
更嚴重的是,它把工具函數直接掛載到HTTP路由,例如:
# 錯誤示范:工具函數直連路由
@app.route('/mcp/tools/query', methods=['POST'])
def query_tool():
# 這里沒有檢查調用方是否有query權限
return execute_sql(request.json['query']) # ← 直接執行用戶輸入結果是:任何持有有效JWT(哪怕只是anon角色)的請求,都能觸發任意SQL。
2. Agent調用鏈無邊界控制
MCP協議允許Agent間相互調用,但Supabase插件未限制調用深度和目標范圍。攻擊者發現:
- Agent A(低權限)可調用Agent B(高權限)
- Agent B在執行時未校驗調用來源,直接信任傳入參數
- 最終形成跳板:
anon → data_cleaner → model_inference → db_admin
日志顯示,一次攻擊鏈包含7次跨Agent調用,其中3個Agent運行在相同進程內,共享內存空間——權限隔離徹底失效。
實操方案:現在就能加上的防護
權限校驗必須嵌入工具層
不要依賴中間件統一鑒權。每個工具函數啟動時,必須顯式檢查:
- 調用方JWT中的
scope字段是否包含當前工具ID - 請求參數是否在預定義schema內(用Pydantic或JSON Schema)
- 數據庫操作是否限定在租戶schema下(如
tenant_123.users而非public.users)
from pydantic import BaseModel, Field
from typing import Literal
class QueryToolInput(BaseModel):
query: str = Field(..., max_length=2048)
mode: Literal["read", "explain"] = "read" # 禁止write/delete
@app.route('/mcp/tools/query', methods=['POST'])
@token_required
def query_tool():
try:
input_data = QueryToolInput.model_validate(request.json)
except Exception as e:
return jsonify({"error": "Invalid input"}), 400
# 關鍵:從token提取租戶ID和權限
token_payload = jwt.decode(
request.headers['Authorization'],
key=SECRET_KEY,
algorithms=["HS256"]
)
if "query" not in token_payload.get("scope", []):
return jsonify({"error": "Permission denied"}), 403
# 強制重寫SQL前綴
safe_query = f"SET search_path TO tenant_{token_payload['tenant_id']}; {input_data.query}"
# 攔截危險操作
if any(kw in safe_query.lower() for kw in ["drop", "delete", "copy", "create"]):
return jsonify({"error": "Write operations forbidden"}), 403
return jsonify(execute_sql(safe_query))Agent調用必須帶簽名和超時
禁止裸HTTP調用。所有Agent間通信需滿足:
- 調用方用私鑰對請求體簽名,接收方用公鑰驗簽
- 每次調用附帶
caller_id和ttl(建議≤5秒) - 接收方拒絕
ttl過期或caller_id不在白名單的請求
import hmac
import time
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_pem_public_key
def verify_agent_call(payload: dict, signature: str, public_key_pem: str) -> bool:
# 驗證簽名
pub_key = load_pem_public_key(public_key_pem.encode())
try:
pub_key.verify(
bytes.fromhex(signature),
payload['body'].encode(),
padding.PKCS1v15(),
hashes.SHA256()
)
except Exception:
return False
# 驗證時效性
if time.time() - payload['timestamp'] > 5:
return False
# 驗證調用方白名單
if payload['caller_id'] not in ALLOWED_AGENTS:
return False
return True
@app.route('/api/call_agent', methods=['POST'])
def call_agent():
data = request.json
if not verify_agent_call(
data,
request.headers.get('X-Signature'),
AGENT_PUBLIC_KEYS[data['target_id']]
):
return jsonify({"error": "Invalid call"}), 403
# 執行調用...安全不是功能,是部署約束
合規Agent的變現能力,取決于它能否通過以下硬性檢查:
- 租戶隔離:每個請求必須綁定
tenant_id,數據庫連接池按租戶分隔 - 資源熔斷:單次工具調用CPU時間>2s或內存>100MB時強制終止
- 審計留痕:所有工具調用記錄
caller_id、tool_id、input_hash、duration_ms到獨立審計表
沒有這些,所謂“安全增值服務”只是營銷話術。用戶真正付費的,是能寫進SLA的確定性保障——比如“SQL注入防護覆蓋率100%”、“跨租戶數據泄露零事件”。
立即行動清單
- 檢查你的MCP Server:搜索代碼中所有
@app.route裝飾器,確認每個工具路由是否包含scope校驗和參數schema驗證 - 禁用危險工具:臨時移除
execute_sql、run_shell等高危工具,改用預編譯查詢模板 - 強制租戶上下文:在所有數據庫操作前插入
SET search_path TO tenant_xxx,并在連接池初始化時綁定 - 添加調用簽名:為Agent間通信生成RSA密鑰對,要求所有
/api/call_agent請求攜帶X-Signature頭
別等下一個漏洞。現在就打開編輯器,刪掉那行沒校驗scope的return execute_sql()。