Supabase MCP安全漏洞詳解:allow_unrestricted_queries配置錯(cuò)誤致PostgreSQL全庫泄露
Supabase MCP安全漏洞實(shí)戰(zhàn)警示:SQL數(shù)據(jù)庫全量泄露是怎么發(fā)生的?
MCP配置錯(cuò)誤導(dǎo)致全庫泄露的真實(shí)案例
Hacker News上有個(gè)開發(fā)者貼出自己的事故復(fù)盤:他用Supabase MCP連接PostgreSQL,只改了一行配置,結(jié)果整個(gè)數(shù)據(jù)庫被爬空。
問題出在supabase.toml里這行:
[database]
# 錯(cuò)誤配置:允許任意客戶端執(zhí)行任意查詢
allow_unrestricted_queries = trueMCP Server啟動(dòng)后,攻擊者直接連上mcp://<host>:6543,發(fā)了個(gè)SELECT * FROM users;,接著是SELECT * FROM payments;,最后把整庫pg_dump走。沒有認(rèn)證繞過,沒有0day,純屬配置放開+暴露端口。
MCP協(xié)議本身不危險(xiǎn),危險(xiǎn)的是默認(rèn)行為和權(quán)限模型
MCP不是新協(xié)議,它是Supabase為簡化多云數(shù)據(jù)同步封裝的一層gRPC/HTTP網(wǎng)關(guān)。它的風(fēng)險(xiǎn)點(diǎn)很具體:
- 端口暴露即入口:MCP Server默認(rèn)監(jiān)聽
6543(非PostgreSQL原生端口),但只要防火墻放行、沒配TLS或IP白名單,就等于把數(shù)據(jù)庫門把手焊死在大街上 - 權(quán)限繼承混亂:MCP不自動(dòng)繼承PostgreSQL的行級(jí)策略(RLS)。你數(shù)據(jù)庫里開了
ENABLE ROW LEVEL SECURITY,MCP照樣能繞過——除非顯式啟用enable_rls = true并綁定策略函數(shù) - 角色映射缺失:Supabase Auth的
auth.uid()在MCP上下文中默認(rèn)不可用。不手動(dòng)注入x-user-id頭或配置JWT解析,所有請(qǐng)求都以postgres超級(jí)用戶身份執(zhí)行
三個(gè)必須落地的防護(hù)動(dòng)作
1. 權(quán)限隔離:砍掉所有“全局”權(quán)限
Supabase MCP不支持GRANT ALL ON DATABASE這種粗粒度授權(quán)。必須按表、按列、按操作精確控制:
// 正確做法:創(chuàng)建專用MCP角色,僅授予必要權(quán)限
await supabase.rpc('create_mcp_role', {
role_name: 'mcp_reader',
schema: 'public',
table: 'orders',
columns: ['id', 'status', 'created_at'],
permissions: ['SELECT']
});對(duì)應(yīng)SQL:
-- 在PostgreSQL中執(zhí)行
CREATE ROLE mcp_reader;
GRANT USAGE ON SCHEMA public TO mcp_reader;
GRANT SELECT(id, status, created_at) ON TABLE public.orders TO mcp_reader;
-- 禁止該角色訪問敏感表
REVOKE ALL ON TABLE public.payments FROM mcp_reader;2. 策略校驗(yàn):用RLS兜底,別信應(yīng)用層過濾
MCP支持透傳RLS策略,但需要顯式開啟。在supabase.toml中:
[database]
# 必須設(shè)為true,否則RLS完全失效
enable_rls = true
[database.rls_policies]
# 綁定策略函數(shù)到具體表
"public.orders" = "user_can_read_order"對(duì)應(yīng)的策略函數(shù):
CREATE POLICY user_can_read_order ON public.orders
FOR SELECT
USING (auth.uid() = user_id OR is_admin());3. RBAC集成:把Auth角色映射到MCP會(huì)話
MCP Server啟動(dòng)時(shí)需加載Supabase JWT密鑰,并在請(qǐng)求頭中傳遞Authorization: Bearer <token>:
# 啟動(dòng)命令必須包含JWT驗(yàn)證配置
supabase start --mcp-jwt-secret=your-supabase-jwt-secret服務(wù)端自動(dòng)解析role字段,映射到PostgreSQL角色。不需要手寫setAuth()——那是前端SDK的邏輯,MCP Server要的是后端可信身份。
復(fù)盤那個(gè)被拖庫的配置
回到開頭的事故,真正的問題鏈?zhǔn)牵?/p>
supabase.toml中allow_unrestricted_queries = true(默認(rèn)為false)- 防火墻開放了
6543端口給0.0.0.0/0 - 沒啟用
enable_rls = true,PostgreSQL的RLS策略形同虛設(shè) - 數(shù)據(jù)庫連接池用的是
postgres超級(jí)用戶,而非最小權(quán)限角色
修復(fù)只需三步:
- 改
allow_unrestricted_queries = false - 改
enable_rls = true并綁定策略 - 把
DATABASE_URL里的postgres換成專用角色mcp_reader
別依賴課程,先做這三件事
安全不是學(xué)出來的,是改出來的:
- 立刻檢查你的
supabase.toml:搜allow_unrestricted_queries和enable_rls,確認(rèn)值符合生產(chǎn)要求 - 登錄數(shù)據(jù)庫執(zhí)行
\du:看是否存在mcp_*專用角色,且沒被賦予SUPERUSER或CREATEDB權(quán)限 - 用
nc -zv your-host 6543測試端口:如果公網(wǎng)可連,加防火墻規(guī)則或改用VPC內(nèi)網(wǎng)通信
MCP本身沒問題。出問題的是把數(shù)據(jù)庫當(dāng)玩具配的習(xí)慣。