Supabase MCP權限繞過漏洞分析與安全加固實踐

Supabase MCP漏洞實錄:權限繞過與加固實踐
漏洞現場:全庫SQL導出是怎么發生的
Hacker News上那條“Supabase MCP Server裸奔導出全庫SQL”的帖子不是危言聳聽。真實情況是:攻擊者發一個特制的RPC請求,繞過所有權限檢查,直接拿到pg_catalog里所有表結構、索引、視圖定義,再拼出SELECT * FROM ...語句批量執行——整個數據庫的SQL schema和數據就這么流出去了。
這不是協議缺陷,是Supabase的MCP Server實現漏掉了關鍵校驗點。
權限校驗在哪斷了鏈
問題出在元數據接口的處理邏輯里。MCP規范要求每個RPC調用必須經過三道關卡:
- 調用者身份可信(JWT簽名有效)
- 請求操作在授權范圍內(如
read:tables) - 目標資源屬于該調用者可訪問的租戶/項目(租戶隔離)
但Supabase的list_tables和get_table_schema這兩個接口跳過了第二、第三步。只要能連上Server,任何未認證或低權限用戶都能調用它們。
結果就是:一個本該只讀自己表的前端Agent,能查出整個PostgreSQL實例里所有數據庫的全部表結構。
這不是MCP的鍋,是實現沒守規矩
MCP協議本身有明確的權限模型:每個RPC方法必須聲明所需權限(required_permissions: ["read:tables"]),Server必須在執行前校驗。沙箱機制也規定了Agent默認無權訪問pg_catalog、無法跨數據庫查詢。
這次被“撞穿”的不是協議邊界,而是開發者把協議當擺設——寫了RPC handler,卻忘了加if !ctx.HasPermission("read:tables") { return err }。
MCP安全機制:就兩件事,別搞復雜
MCP的安全不靠玄學,靠兩根柱子:權限控制和沙箱隔離。其他都是這兩根柱子的延伸。
權限控制:每個RPC入口必須校驗
MCP的權限是動詞+名詞組合:read:tables、write:rows、exec:sql。不是角色RBAC,是操作級ABAC。
關鍵規則:
- 每個注冊的RPC handler開頭必須做權限檢查
- 權限檢查必須包含租戶上下文(
ctx.TenantID())和資源路徑(req.Param("table_name")) - 拒絕時返回標準錯誤
mcp.ErrPermissionDenied,不暴露拒絕原因
server.Register("query", func(ctx mcp.Context, req mcp.Request) (mcp.Response, error) {
table := req.Param("table")
// 必須同時校驗:權限 + 租戶歸屬 + 表白名單
if !ctx.HasPermission("read:rows") ||
ctx.TenantID() != getTableTenant(table) ||
!isAllowedTable(table) {
return nil, mcp.ErrPermissionDenied
}
// ...
})沙箱隔離:資源訪問必須顯式授權
MCP沙箱不是Docker容器,是運行時約束:
- Agent進程默認無文件系統訪問權限(
os.Open直接panic) - 網絡只能連預注冊的endpoint(如
supabase.com:5432) - 數據庫連接自動注入租戶schema前綴(
tenant_123.users)
違反規則的代碼會當場失敗:
// ? 這行會panic:未授權的文件訪問
data, _ := os.ReadFile("/etc/passwd")
// ? 正確方式:通過MCP提供的安全API
content, err := ctx.ReadFile("config.yaml") // 僅限Agent工作目錄Supabase漏洞復盤:兩個硬傷
Supabase的修復補丁(v1.23.1)暴露了最初的問題根源:
1. 元數據接口完全裸奔
list_schemas、get_column_info這些方法注冊時沒聲明任何權限要求:
// 錯誤示例(原始代碼)
server.Register("list_schemas", listSchemasHandler) // 沒配required_permissions導致MCP Server的全局權限中間件直接跳過校驗。
2. 沙箱配置被手動繞過
為支持“管理員調試模式”,Supabase在啟動時加了這個flag:
// 危險!生產環境絕對禁用
if os.Getenv("DEBUG_UNSAFE") == "true" {
disableSandbox()
}而某些部署文檔里寫著“設置DEBUG_UNSAFE=true啟用高級功能”——于是沙箱形同虛設。
安全加固:三步落地
別談理論,直接上能跑的代碼。
第一步:強制權限校驗中間件
在Server初始化時注入全局校驗:
server.Use(func(next mcp.Handler) mcp.Handler {
return func(ctx mcp.Context, req mcp.Request) (mcp.Response, error) {
// 從RPC注冊信息里讀取聲明的權限
perms := server.GetRequiredPermissions(req.Method())
for _, p := range perms {
if !ctx.HasPermission(p) {
return nil, mcp.ErrPermissionDenied
}
}
return next(ctx, req)
}
})第二步:沙箱策略硬編碼
禁止運行時修改沙箱配置:
// 初始化時鎖定沙箱
sandbox := mcp.NewSandbox()
sandbox.AllowNetwork("supabase.com:5432")
sandbox.AllowFileRead("config/", "secrets/") // 僅限指定目錄
sandbox.Lock() // 后續調用disableSandbox()會panic第三步:最小權限原則落地
給每個Agent分配獨立Token,權限精確到表:
{
"permissions": ["read:rows"],
"resources": ["users", "profiles"],
"tenant_id": "prod_abc123"
}而不是給前端Agent一個*:*通配符權限。
商業化前提:先守住底線
安全不是成本,是準入門檻。所有變現路徑都建立在“用戶信你不會丟數據”的基礎上。
可落地的合規路徑
- 按查詢計費:Agent每次調用
query方法,Server記錄tenant_id + table_name + row_count,生成賬單。權限校驗確保只計費被允許的查詢。 - Agent市場審核:上架Agent必須通過沙箱掃描(檢測是否調用
os/exec、net.Dial等危險API),權限聲明必須匹配實際行為。 - 數據代理服務:用戶授權后,Agent以
read:anonymized_rows權限運行,在內存中脫敏(如email字段替換為hash),再返回結果——全程不落盤。
下一步:動手改一行代碼
現在打開你的MCP Server代碼,找到任意一個RPC handler。在第一行插入:
if !ctx.HasPermission("your_required_permission") {
return nil, mcp.ErrPermissionDenied
}然后刪掉所有DEBUG_UNSAFE環境變量相關邏輯。
做完這兩件事,你就比Supabase早半年避開這個漏洞。