🧩 MCP生态

Supabase MCP插件高危SQL泄露风险解析:协议安全与元数据接口防护方案

发布时间:2026-04-14 分类: MCP生态
摘要:Supabase MCP插件高危SQL泄露风险解析:协议安全是Agent商业化的生命线事件回顾:Supabase MCP插件的“裸奔”危机Supabase 是一个开源的 Firebase 替代方案,提供实时数据库、身份验证和 API 自动生成能力。它的 MCP(Multi-Cloud Protocol)插件本意是简化跨云数据协作,但近期被发现存在严重设计缺陷:未校验的协议调用 + 默认暴露的...

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 开发中的三个具体疏漏

  1. 元数据接口无访问边界
    插件把 GET /v1/metadata/* 路由直接映射到内部 schema 查询逻辑,中间跳过了项目租户隔离层。结果是:任意请求都能看到当前数据库实例下的全部 schema —— 不管该请求声称代表哪个 Supabase 项目。
  2. MCP 调用未绑定会话上下文
    MCP 协议消息里带 project_id 字段,但插件未校验该 ID 是否与请求携带的 JWT 中声明的 subaud 匹配。攻击者可篡改 project_id 值,读取其他客户的表结构。
  3. 缺乏最小权限原则的默认配置
    生产环境默认启用 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 多聪明,而是我们敢把生产数据库交给你。”

下一步:现在就做的四件事

  1. 立刻检查你部署的 MCP Server
    curl -I https://your-mcp-server/v1/metadata/tables —— 如果返回 200 OK,马上关掉。
  2. 删掉所有 ENABLE_* 类型的环境变量
    改用数据库字段或配置中心控制功能开关,确保每个项目独立决策。
  3. list_tables 前加一道租户校验 SQL
    即使只是 SELECT 1 FROM projects WHERE id = $1 AND metadata_enabled = true,也比没有强。
  4. 把本文的 CI 检查脚本加进 pre-commit hook
    开发者本地提交代码前就跑一遍,防患于未然。

安全不是功能列表里最后一项待办事项。它是你第一个 git commit 就该写进 README 的那行字:「本服务默认不暴露任何元数据,除非你明确告诉它要暴露给谁。」

返回首页