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

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