Supabase MCP權(quán)限繞過漏洞分析與安全加固實(shí)踐

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