MCP協(xié)議配置不當致數(shù)據(jù)庫裸奔:SQL注入風險與安全配置指南

MCP竟成數(shù)據(jù)庫裸奔開關?——從Hacker News熱議案例看MCP協(xié)議配置不當?shù)闹旅L險
直擊痛點:你的AI項目是否正暴露在SQL注入的威脅之下?
Hacker News上一則帖子火了:一家AI創(chuàng)業(yè)公司用Supabase做后端,MCP Server配錯了,整個數(shù)據(jù)庫的SQL查詢邏輯直接裸奔。攻擊者拿到一個普通用戶token,就能查遍所有表、拖走全部數(shù)據(jù)。
這不是理論風險。我們翻過幾份真實MCP Server代碼,發(fā)現(xiàn)類似配置錯誤高頻出現(xiàn):權限開得過大、元數(shù)據(jù)接口沒關、輸入校驗形同虛設。
如果你正在用MCP協(xié)議連數(shù)據(jù)庫,別跳過這一節(jié)。
案例回顧:Supabase中的MCP協(xié)議漏洞如何導致全庫SQL泄露?
這家公司做了三件事,把數(shù)據(jù)庫大門鑰匙焊死在門把手上:
1. Server端權限設為*
MCP Server配置里寫了"permissions": ["*"]。任何通過身份驗證的用戶(哪怕只是登錄態(tài)的前端用戶)都能執(zhí)行任意SQL。Supabase的Row Level Security(RLS)策略完全失效——因為MCP Server繞過了RLS,直連PostgreSQL。
2. 元數(shù)據(jù)接口開著公網(wǎng)入口
/v1/metadata/tables 和 /v1/metadata/columns 這兩個端點沒加鑒權,也沒走內網(wǎng)。攻擊者curl一下,立刻拿到所有表名、字段類型、主鍵關系。下一步就是拼UNION SELECT語句。
3. 訪問控制粒度停留在“有無token”層面
沒有角色區(qū)分,沒有表級白名單,沒有字段掩碼。一個客服賬號和DBA賬號在MCP Server眼里毫無區(qū)別。
結果:攻擊者用' OR 1=1 --觸發(fā)報錯,看到完整SQL模板;再構造SELECT * FROM users,拿到明文郵箱和密碼哈希;最后用COPY users TO PROGRAM 'curl -X POST ...'外傳全量數(shù)據(jù)。
根本原因解析:MCP協(xié)議配置不當?shù)闹旅毕?/h2>
MCP本身不背鍋。問題出在開發(fā)者把MCP Server當成了“帶認證的psql客戶端”,忽略了它本質是數(shù)據(jù)庫代理層。
1. 權限管理被當成可選項
MCP規(guī)范明確要求Server實現(xiàn)permissions字段校驗,但很多實現(xiàn)直接忽略或硬編碼為["*"]。更危險的是,部分SDK自動生成的配置模板就默認開全權限。
2. 元數(shù)據(jù)接口設計即暴露面
/metadata不是調試后門,它是生產(chǎn)環(huán)境的高危端點。Supabase官方文檔強調:“禁用元數(shù)據(jù)端點是生產(chǎn)部署的強制步驟”,但沒人讀。
3. 訪問控制邏輯與數(shù)據(jù)庫脫鉤
MCP Server應該繼承數(shù)據(jù)庫的權限模型(比如PostgreSQL的role+schema+table三級權限),而不是另起一套粗粒度RBAC。常見錯誤是只校驗token有效性,不校驗該token對應的角色能否訪問目標表。
4. 日志缺失導致攻擊靜默進行
所有SQL執(zhí)行日志只寫到stdout,沒落盤、沒告警、沒采樣。攻擊者執(zhí)行500次SELECT,運維系統(tǒng)零告警。
關鍵防御點:如何構建安全的MCP Server?
1. 最小權限原則必須落地
刪除所有
["*"]配置,顯式聲明每個角色的權限:{ "role": "analyst", "permissions": [ {"table": "orders", "actions": ["SELECT"]}, {"table": "products", "actions": ["SELECT"]} ] }- 禁用
INSERT/UPDATE/DELETE權限,除非業(yè)務強依賴。讀多寫少場景下,90%的MCP調用只需SELECT。 - 定期用腳本掃描配置文件,自動告警未聲明權限的表。
2. 訪問控制必須綁定數(shù)據(jù)庫原生能力
不要自己實現(xiàn)表級ACL。讓MCP Server調用PostgreSQL的
has_table_privilege()函數(shù)校驗:SELECT has_table_privilege('analyst_role', 'orders', 'SELECT');對敏感字段(如
users.email,payments.card_number)啟用動態(tài)列掩碼,在SQL執(zhí)行前重寫查詢:-- 原始請求 SELECT id, email FROM users WHERE id = 123; -- 實際執(zhí)行(對非admin角色) SELECT id, '***@***.com' AS email FROM users WHERE id = 123;
3. 輸入校驗不是防SQL注入的主力,而是最后一道保險
參數(shù)化查詢必須強制啟用。禁止任何字符串拼接SQL:
# ? 危險 cursor.execute(f"SELECT * FROM {table} WHERE id = {user_input}") # ? 正確(使用pg8000或asyncpg的參數(shù)占位) cursor.execute("SELECT * FROM users WHERE id = $1", [user_id])對表名、字段名等標識符,用白名單校驗:
ALLOWED_TABLES = {"users", "orders", "products"} if table_name not in ALLOWED_TABLES: raise PermissionError("Table not allowed")
4. 元數(shù)據(jù)接口必須物理隔離
- 生產(chǎn)環(huán)境徹底刪除
/metadata路由。調試需求用本地supabase db remote schema pull替代。 如果必須保留,加兩道鎖:
- 只允許
127.0.0.1和CI/CD服務器IP訪問 - 強制HTTP Basic Auth,憑證單獨保管,不進Git
- 只允許
5. 日志必須包含可追溯的上下文
每條SQL執(zhí)行日志至少記錄:
- 請求ID(用于鏈路追蹤)
- 用戶角色(不是用戶名,是實際生效的DB role)
- 表名 + 字段數(shù) + 掃描行數(shù)(
EXPLAIN (FORMAT JSON)第一層) - 執(zhí)行耗時(毫秒級)
示例日志:
[req-abc123] analyst@prod SELECT orders.id,orders.status FROM orders WHERE created_at > '2024-01-01' | rows=1240 | time=84ms6. 安全審計不能靠自覺
每次MCP Server發(fā)布前,運行
mcp-audit工具(開源,見GitHub yitb/mcp-audit):- 檢查配置中是否存在
["*"] - 掃描代碼中是否有
cursor.execute(字符串拼接 - 驗證
/metadata路由是否注冊
- 檢查配置中是否存在
- 每季度請第三方做滲透測試,重點打
/query端點的SQL注入和權限越界。
實戰(zhàn)案例:如何應用上述原則構建安全的MCP Server?
下面是一個精簡但生產(chǎn)可用的MCP Server核心邏輯(基于FastAPI):
from fastapi import FastAPI, HTTPException, Depends, Header
from sqlalchemy import text
from typing import List, Dict, Any
app = FastAPI()
# 從配置加載權限白名單(JSON文件或DB)
PERMISSIONS = {
"analyst": [{"table": "orders", "actions": ["SELECT"]}],
"support": [{"table": "users", "actions": ["SELECT"]}]
}
def get_user_role(x_role: str = Header(..., alias="X-Role")) -> str:
if x_role not in PERMISSIONS:
raise HTTPException(403, "Invalid role")
return x_role
@app.post("/query")
def execute_query(
query: Dict[str, Any],
role: str = Depends(get_user_role)
):
table = query.get("table")
action = query.get("action", "SELECT").upper()
# 1. 白名單校驗
allowed = False
for perm in PERMISSIONS[role]:
if perm["table"] == table and action in perm["actions"]:
allowed = True
break
if not allowed:
raise HTTPException(403, f"Role {role} cannot {action} table {table}")
# 2. 字段掩碼(示例:隱藏email)
if table == "users" and "email" in query.get("columns", []):
query["columns"].remove("email")
query["columns"].append("REPLACE(email, '@', '*') AS email")
# 3. 參數(shù)化執(zhí)行(假設用asyncpg)
sql = f"SELECT {', '.join(query['columns'])} FROM {table}"
if "where" in query:
sql += f" WHERE {query['where']}"
# 實際執(zhí)行時用 $1, $2 占位符,此處省略參數(shù)綁定細節(jié)
return {"result": run_sql(sql, query.get("params", []))}關鍵點:
- 權限檢查在SQL生成前完成,不依賴數(shù)據(jù)庫返回錯誤
- 字段掩碼在SQL拼裝階段介入,避免敏感字段進入查詢
X-Role頭由上游網(wǎng)關(如Traefik)根據(jù)JWT claim注入,不信任客戶端傳值
下一步行動:如何夯實你的MCP Server安全能力?
- 立刻檢查你的MCP Server配置:搜
"permissions": \["\*\]"、/metadata路由、cursor.execute(字符串拼接 - 禁用元數(shù)據(jù)接口:刪掉相關路由,或加IP+Basic Auth雙鎖
- 部署日志采樣:用Loki+Grafana監(jiān)控
/query端點的rows_scanned > 1000事件 - 參考加固清單:m.gsdl.org.cn/mcp-hardening-checklist(開源,含Terraform檢測腳本)
安全不是功能列表里的最后一項。它是MCP Server啟動時第一個必須通過的健康檢查。