Supabase MCP插件高危SQL泄露風(fēng)險解析:協(xié)議安全與元數(shù)據(jù)接口防護方案
Supabase MCP插件高危SQL泄露風(fēng)險解析:協(xié)議安全是Agent商業(yè)化的生命線
事件回顧:Supabase MCP插件的“裸奔”危機
Supabase 是一個開源的 Firebase 替代方案,提供實時數(shù)據(jù)庫、身份驗證和 API 自動生成能力。它的 MCP(Multi-Cloud Protocol)插件本意是簡化跨云數(shù)據(jù)協(xié)作,但近期被發(fā)現(xiàn)存在嚴(yán)重設(shè)計缺陷:未校驗的協(xié)議調(diào)用 + 默認(rèn)暴露的元數(shù)據(jù)接口 = 數(shù)據(jù)庫結(jié)構(gòu)完全可見。
攻擊者無需認(rèn)證,只需向插件暴露的端點發(fā)起 HTTP GET 請求,就能拿到完整的表結(jié)構(gòu):
GET /v1/metadata/tables響應(yīng)中包含所有表名、字段名、類型、主鍵、外鍵、索引,甚至注釋。這意味著:
- 攻擊者能精準(zhǔn)構(gòu)造 SQL 注入或越權(quán)查詢
- 可批量導(dǎo)出敏感字段(如
users.email,orders.amount) - 為后續(xù)橫向移動提供完整攻擊地圖
這個接口在默認(rèn)配置下開啟,且不校驗來源 IP、不檢查 JWT、不驗證調(diào)用方是否已授權(quán)訪問目標(biāo)項目——相當(dāng)于把數(shù)據(jù)庫的 ER 圖貼在服務(wù)器門口。
風(fēng)險成因:MCP 協(xié)議與 Server 實現(xiàn)的錯位
MCP 協(xié)議本身不危險,危險的是實現(xiàn)方式
MCP 是一套定義跨云服務(wù)如何交換元數(shù)據(jù)和執(zhí)行操作的規(guī)范。它要求服務(wù)提供統(tǒng)一的 list_tables, get_schema, execute_sql 等方法。問題不在于這些方法存在,而在于:
- 規(guī)范未強制要求認(rèn)證/授權(quán)語義(只說“應(yīng)支持”,沒說“必須校驗”)
- 實現(xiàn)者誤將“協(xié)議可調(diào)用”等同于“接口可公開”
- 元數(shù)據(jù)接口被當(dāng)作調(diào)試便利功能,而非生產(chǎn)級敏感端點
Server 開發(fā)中的三個具體疏漏
- 元數(shù)據(jù)接口無訪問邊界
插件把GET /v1/metadata/*路由直接映射到內(nèi)部 schema 查詢邏輯,中間跳過了項目租戶隔離層。結(jié)果是:任意請求都能看到當(dāng)前數(shù)據(jù)庫實例下的全部 schema —— 不管該請求聲稱代表哪個 Supabase 項目。 - MCP 調(diào)用未綁定會話上下文
MCP 協(xié)議消息里帶project_id字段,但插件未校驗該 ID 是否與請求攜帶的 JWT 中聲明的sub或aud匹配。攻擊者可篡改project_id值,讀取其他客戶的表結(jié)構(gòu)。 - 缺乏最小權(quán)限原則的默認(rèn)配置
生產(chǎn)環(huán)境默認(rèn)啟用metadata功能模塊,且未提供一鍵關(guān)閉開關(guān)。更關(guān)鍵的是,沒有區(qū)分「開發(fā)調(diào)試」和「生產(chǎn)運行」兩套配置模式。.env里一行ENABLE_METADATA=false就能阻斷大部分攻擊面,但它不存在。
防御方案:四條可落地的技術(shù)動作
1. 強制協(xié)議調(diào)用綁定可信上下文
MCP 請求必須攜帶有效憑證,并在校驗后與操作上下文強綁定:
// 示例:Express 中間件校驗邏輯
app.use('/v1/mcp', async (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) return res.status(401).end();
const token = authHeader.split(' ')[1];
const payload = verifyJWT(token); // 驗證簽名 & exp & aud
// 關(guān)鍵:將 project_id 從 JWT 中提取,而非信任請求 body
req.projectId = payload.aud; // 或 payload.sub,取決于你的租戶模型
next();
});后續(xù)所有 MCP 方法(如 list_tables)都必須使用 req.projectId 構(gòu)造查詢,而不是解析請求體里的 project_id。
2. 元數(shù)據(jù)接口默認(rèn)關(guān)閉,按需顯式啟用
- 刪除
ENABLE_METADATA=true這類全局開關(guān) - 改為按項目粒度控制:在 Supabase 項目設(shè)置中新增「暴露元數(shù)據(jù)」布爾選項
后端查詢前加檢查:
SELECT enabled FROM projects WHERE id = $1;- 若禁用,直接返回
403 Forbidden,不碰數(shù)據(jù)庫 schema 表
3. 對元數(shù)據(jù)做動態(tài)脫敏,而非全量透出
即使客戶主動啟用了元數(shù)據(jù)接口,也不應(yīng)返回原始 schema。改為:
- 隱藏真實表名/字段名(返回別名或哈希值,僅限調(diào)試會話)
- 過濾敏感字段:自動跳過含
email,phone,ssn,password等關(guān)鍵詞的列 - 移除注釋、索引詳情、約束條件等非必要信息
// 原始響應(yīng)(危險)
{
"name": "users",
"columns": [
{ "name": "email", "type": "text", "comment": "user's verified email" }
]
}
// 脫敏后(安全)
{
"name": "t_8a3f",
"columns": [
{ "name": "c_2b9e", "type": "text" }
]
}4. 把安全檢查寫進 CI/CD 流水線
在每次構(gòu)建 MCP Server 時自動執(zhí)行:
- 掃描所有路由,標(biāo)記未加
auth中間件的/v1/metadata/*和/v1/mcp/*端點 → 失敗 - 檢查
docker-compose.yml或 Helm chart 中是否包含ENABLE_METADATA環(huán)境變量 → 失敗 - 運行最小化滲透測試腳本,嘗試無認(rèn)證訪問元數(shù)據(jù)接口 → 失敗則阻斷發(fā)布
# 測試腳本片段
if curl -s -o /dev/null -w "%{http_code}" http://localhost:54321/v1/metadata/tables | grep -q "200"; then
echo "FAIL: metadata endpoint exposed without auth"
exit 1
fi真實案例:一家客服 Agent 公司怎么做
他們用 Supabase MCP 插件連接銀行客戶的數(shù)據(jù)庫,處理工單分類和退款審批。面對同樣風(fēng)險,他們做了三件事:
- 砍掉所有通用元數(shù)據(jù)接口:只保留一個
POST /v1/mcp/validate_query,輸入 SQL 片段,返回「語法合法」或「字段不存在」,不返回任何 schema 信息 - SQL 執(zhí)行加白名單:
execute_sql方法只允許SELECT,且必須匹配預(yù)設(shè)模板(如SELECT * FROM tickets WHERE status = ? AND created_at > ?),參數(shù)類型和范圍嚴(yán)格校驗 - 每條日志帶租戶指紋:Nginx 日志格式中加入
$http_x_project_id,ELK 里可秒級定位某次異常 schema 查詢來自哪個客戶
結(jié)果:通過 PCI DSS 二級認(rèn)證,簽下 7 家區(qū)域性銀行,年合同額 520 萬元。客戶明確表示:“不是你們 Agent 多聰明,而是我們敢把生產(chǎn)數(shù)據(jù)庫交給你?!?/p>
下一步:現(xiàn)在就做的四件事
- 立刻檢查你部署的 MCP Server
curl -I https://your-mcp-server/v1/metadata/tables—— 如果返回200 OK,馬上關(guān)掉。 - 刪掉所有
ENABLE_*類型的環(huán)境變量
改用數(shù)據(jù)庫字段或配置中心控制功能開關(guān),確保每個項目獨立決策。 - 在
list_tables前加一道租戶校驗 SQL
即使只是SELECT 1 FROM projects WHERE id = $1 AND metadata_enabled = true,也比沒有強。 - 把本文的 CI 檢查腳本加進 pre-commit hook
開發(fā)者本地提交代碼前就跑一遍,防患于未然。
安全不是功能列表里最后一項待辦事項。它是你第一個 git commit 就該寫進 README 的那行字:「本服務(wù)默認(rèn)不暴露任何元數(shù)據(jù),除非你明確告訴它要暴露給誰?!?/p>