Supabase MCP插件高危SQL泄露风险解析:协议安全与元数据接口防护方案
Supabase MCP插件高危SQL泄露风险解析:协议安全是Agent商业化的生命线
事件回顾:Supabase MCP插件的“裸奔”危机
Supabase 是一个开源的 Firebase 替代方案,提供实时数据库、身份验证和 API 自动生成能力。它的 MCP(Multi-Cloud Protocol)插件本意是简化跨云数据协作,但近期被发现存在严重设计缺陷:未校验的协议调用 + 默认暴露的元数据接口 = 数据库结构完全可见。
攻击者无需认证,只需向插件暴露的端点发起 HTTP GET 请求,就能拿到完整的表结构:
GET /v1/metadata/tables响应中包含所有表名、字段名、类型、主键、外键、索引,甚至注释。这意味着:
- 攻击者能精准构造 SQL 注入或越权查询
- 可批量导出敏感字段(如
users.email,orders.amount) - 为后续横向移动提供完整攻击地图
这个接口在默认配置下开启,且不校验来源 IP、不检查 JWT、不验证调用方是否已授权访问目标项目——相当于把数据库的 ER 图贴在服务器门口。
风险成因:MCP 协议与 Server 实现的错位
MCP 协议本身不危险,危险的是实现方式
MCP 是一套定义跨云服务如何交换元数据和执行操作的规范。它要求服务提供统一的 list_tables, get_schema, execute_sql 等方法。问题不在于这些方法存在,而在于:
- 规范未强制要求认证/授权语义(只说“应支持”,没说“必须校验”)
- 实现者误将“协议可调用”等同于“接口可公开”
- 元数据接口被当作调试便利功能,而非生产级敏感端点
Server 开发中的三个具体疏漏
- 元数据接口无访问边界
插件把GET /v1/metadata/*路由直接映射到内部 schema 查询逻辑,中间跳过了项目租户隔离层。结果是:任意请求都能看到当前数据库实例下的全部 schema —— 不管该请求声称代表哪个 Supabase 项目。 - MCP 调用未绑定会话上下文
MCP 协议消息里带project_id字段,但插件未校验该 ID 是否与请求携带的 JWT 中声明的sub或aud匹配。攻击者可篡改project_id值,读取其他客户的表结构。 - 缺乏最小权限原则的默认配置
生产环境默认启用metadata功能模块,且未提供一键关闭开关。更关键的是,没有区分「开发调试」和「生产运行」两套配置模式。.env里一行ENABLE_METADATA=false就能阻断大部分攻击面,但它不存在。
防御方案:四条可落地的技术动作
1. 强制协议调用绑定可信上下文
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
// 关键:将 project_id 从 JWT 中提取,而非信任请求 body
req.projectId = payload.aud; // 或 payload.sub,取决于你的租户模型
next();
});后续所有 MCP 方法(如 list_tables)都必须使用 req.projectId 构造查询,而不是解析请求体里的 project_id。
2. 元数据接口默认关闭,按需显式启用
- 删除
ENABLE_METADATA=true这类全局开关 - 改为按项目粒度控制:在 Supabase 项目设置中新增「暴露元数据」布尔选项
后端查询前加检查:
SELECT enabled FROM projects WHERE id = $1;- 若禁用,直接返回
403 Forbidden,不碰数据库 schema 表
3. 对元数据做动态脱敏,而非全量透出
即使客户主动启用了元数据接口,也不应返回原始 schema。改为:
- 隐藏真实表名/字段名(返回别名或哈希值,仅限调试会话)
- 过滤敏感字段:自动跳过含
email,phone,ssn,password等关键词的列 - 移除注释、索引详情、约束条件等非必要信息
// 原始响应(危险)
{
"name": "users",
"columns": [
{ "name": "email", "type": "text", "comment": "user's verified email" }
]
}
// 脱敏后(安全)
{
"name": "t_8a3f",
"columns": [
{ "name": "c_2b9e", "type": "text" }
]
}4. 把安全检查写进 CI/CD 流水线
在每次构建 MCP Server 时自动执行:
- 扫描所有路由,标记未加
auth中间件的/v1/metadata/*和/v1/mcp/*端点 → 失败 - 检查
docker-compose.yml或 Helm chart 中是否包含ENABLE_METADATA环境变量 → 失败 - 运行最小化渗透测试脚本,尝试无认证访问元数据接口 → 失败则阻断发布
# 测试脚本片段
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 插件连接银行客户的数据库,处理工单分类和退款审批。面对同样风险,他们做了三件事:
- 砍掉所有通用元数据接口:只保留一个
POST /v1/mcp/validate_query,输入 SQL 片段,返回「语法合法」或「字段不存在」,不返回任何 schema 信息 - SQL 执行加白名单:
execute_sql方法只允许SELECT,且必须匹配预设模板(如SELECT * FROM tickets WHERE status = ? AND created_at > ?),参数类型和范围严格校验 - 每条日志带租户指纹:Nginx 日志格式中加入
$http_x_project_id,ELK 里可秒级定位某次异常 schema 查询来自哪个客户
结果:通过 PCI DSS 二级认证,签下 7 家区域性银行,年合同额 520 万元。客户明确表示:“不是你们 Agent 多聪明,而是我们敢把生产数据库交给你。”
下一步:现在就做的四件事
- 立刻检查你部署的 MCP Server
curl -I https://your-mcp-server/v1/metadata/tables—— 如果返回200 OK,马上关掉。 - 删掉所有
ENABLE_*类型的环境变量
改用数据库字段或配置中心控制功能开关,确保每个项目独立决策。 - 在
list_tables前加一道租户校验 SQL
即使只是SELECT 1 FROM projects WHERE id = $1 AND metadata_enabled = true,也比没有强。 - 把本文的 CI 检查脚本加进 pre-commit hook
开发者本地提交代码前就跑一遍,防患于未然。
安全不是功能列表里最后一项待办事项。它是你第一个 git commit 就该写进 README 的那行字:「本服务默认不暴露任何元数据,除非你明确告诉它要暴露给谁。」