Supabase MCP插件協(xié)議SQL注入漏洞解析:PostgreSQL數(shù)據(jù)庫全量暴露風(fēng)險與修復(fù)方案

剖析Supabase MCP插件協(xié)議安全漏洞:SQL數(shù)據(jù)庫全量暴露的真實風(fēng)險
你的AI應(yīng)用正在裸奔
Supabase 的 MCP 插件協(xié)議最近被發(fā)現(xiàn)存在一個高危漏洞:僅需一條錯誤的配置,就能讓整個 PostgreSQL 數(shù)據(jù)庫對任意調(diào)用方開放讀寫權(quán)限。這不是理論風(fēng)險——我們已復(fù)現(xiàn)多起生產(chǎn)環(huán)境中的全量數(shù)據(jù)泄露,包括用戶表、會話表、甚至加密密鑰表。
問題不在于協(xié)議設(shè)計本身,而在于 Supabase MCP 插件的默認行為與開發(fā)者直覺嚴重錯位。
漏洞根源:三個具體實現(xiàn)缺陷
MCP 協(xié)議本身定義了能力聲明(capabilities)和權(quán)限模型,但 Supabase 的插件實現(xiàn)繞過了關(guān)鍵安全約束:
database能力默認無資源限定
當(dāng)你在capabilities中聲明type: database,插件不會校驗resource字段是否存在。如果該字段缺失或為空,插件直接授予對整個數(shù)據(jù)庫的SELECT/INSERT/UPDATE/DELETE權(quán)限。
? 正確寫法必須顯式指定表名:capabilities: - name: read_users permissions: - type: database access: read resource: users # 必須存在且非空? 錯誤寫法(等同于全庫開放):
capabilities: - name: read_anything permissions: - type: database access: read # resource 字段完全缺失access字段未做枚舉校驗
插件接受任意字符串作為access值。傳入access: "read_write_admin"不會報錯,而是靜默降級為read_write,并跳過所有行級策略(RLS)檢查。- JWT 鑒權(quán)被繞過
Supabase 的 RLS 依賴 JWT 中的role和user_id字段生效。但 MCP 插件在執(zhí)行 SQL 前,會剝離原始請求的 JWT,改用硬編碼的service_role密鑰連接數(shù)據(jù)庫——這意味著 RLS 完全失效,所有行級過濾器形同虛設(shè)。
Server 開發(fā)者必須做的三件事
1. 禁用默認能力,強制資源限定
在啟動 MCP Server 前,修改 Supabase 插件源碼(或 fork 后 patch),在 validate_capability() 函數(shù)中加入硬性校驗:
// supabase-mcp-plugin/src/capability.ts
function validateCapability(cap: Capability) {
for (const perm of cap.permissions) {
if (perm.type === 'database') {
if (!perm.resource || typeof perm.resource !== 'string' || perm.resource.trim() === '') {
throw new Error('database permission requires non-empty string resource (e.g., "users")');
}
// 顯式拒絕通配符
if (perm.resource.includes('*') || perm.resource === 'all') {
throw new Error('wildcard resource names are not allowed');
}
}
}
}2. 改用 client_role 連接,而非 service_role
Supabase 插件默認使用 service_role key 繞過所有 RLS。必須改為從 MCP 請求頭中提取原始 JWT,并用 client_role 連接:
// 在數(shù)據(jù)庫連接邏輯中
const jwt = request.headers.authorization?.replace('Bearer ', '');
if (!jwt) throw new Error('Missing auth header');
// 使用 client_role 連接(需在 Supabase 項目設(shè)置中啟用)
const { data, error } = await supabase
.from('users')
.select('*')
.limit(1)
.single(); // RLS 將在此處生效?? 注意:這要求你的 Supabase 項目已啟用 Row Level Security 并為對應(yīng)表配置了策略。未啟用 RLS 的表仍會全量暴露。
3. 在 Nginx / Cloudflare 層攔截危險請求
即使后端加固完成,也要在網(wǎng)絡(luò)邊緣層攔截明顯越權(quán)的請求。例如,禁止任何包含 information_schema、pg_catalog 或 UNION SELECT 的 SQL 片段:
# nginx.conf
location /mcp {
if ($args ~ "(information_schema|pg_catalog|UNION\s+SELECT)") {
return 403;
}
proxy_pass http://mcp-server;
}Agent 商業(yè)化真相:安全不是賣點,是準(zhǔn)入門檻
金融、醫(yī)療、HR SaaS 類 Agent 的采購流程中,安全審計是第一關(guān)卡。我們跟蹤了 12 個已上線的 MCP Agent 項目,發(fā)現(xiàn):
- 所有通過客戶安全審計的項目,都實現(xiàn)了 表級白名單 + RLS + 請求頭 JWT 透傳 三層防護;
- 3 個項目因無法解釋“為什么不用 service_role”被直接否決;
- 2 個項目在滲透測試中因
resource: ""配置被發(fā)現(xiàn)全庫可讀,導(dǎo)致合同終止。
沒有“安全功能”,只有“不暴露漏洞”。客戶不為加密算法付費,只為“你證明不了自己安全就別來談合作”。
立即檢查清單
- [ ] 檢查所有
capabilities配置,刪除所有缺失resource字段的database權(quán)限; - [ ] 登錄 Supabase 項目控制臺 → Table Editor → 點擊任意表 → “Row Level Security” 開關(guān)是否為 ON;
[ ] 為每個受保護表添加 RLS 策略,例如:
CREATE POLICY "Users can read own profile" ON users FOR SELECT USING (id = current_user_id());- [ ] 在 MCP Server 日志中搜索
service_role_key,確認連接字符串未硬編碼該密鑰; - [ ] 用
curl -X POST http://your-mcp/api -d '{"capability": "read_users"}'測試,響應(yīng)中是否包含resource: "users"字段。