🧩 MCP生态

MCP协议配置不当致数据库裸奔:SQL注入风险与安全配置指南

发布时间:2026-04-15 分类: MCP生态
摘要:MCP竟成数据库裸奔开关?——从Hacker News热议案例看MCP协议配置不当的致命风险直击痛点:你的AI项目是否正暴露在SQL注入的威胁之下?Hacker News上一则帖子火了:一家AI创业公司用Supabase做后端,MCP Server配错了,整个数据库的SQL查询逻辑直接裸奔。攻击者拿到一个普通用户token,就能查遍所有表、拖走全部数据。这不是理论风险。我们翻过几份真实MCP...

封面

MCP竟成数据库裸奔开关?——从Hacker News热议案例看MCP协议配置不当的致命风险

直击痛点:你的AI项目是否正暴露在SQL注入的威胁之下?

Hacker News上一则帖子火了:一家AI创业公司用Supabase做后端,MCP Server配错了,整个数据库的SQL查询逻辑直接裸奔。攻击者拿到一个普通用户token,就能查遍所有表、拖走全部数据。

这不是理论风险。我们翻过几份真实MCP Server代码,发现类似配置错误高频出现:权限开得过大、元数据接口没关、输入校验形同虚设。

如果你正在用MCP协议连数据库,别跳过这一节。

案例回顾:Supabase中的MCP协议漏洞如何导致全库SQL泄露?

这家公司做了三件事,把数据库大门钥匙焊死在门把手上:

1. Server端权限设为*

MCP Server配置里写了"permissions": ["*"]。任何通过身份验证的用户(哪怕只是登录态的前端用户)都能执行任意SQL。Supabase的Row Level Security(RLS)策略完全失效——因为MCP Server绕过了RLS,直连PostgreSQL。

2. 元数据接口开着公网入口

/v1/metadata/tables/v1/metadata/columns 这两个端点没加鉴权,也没走内网。攻击者curl一下,立刻拿到所有表名、字段类型、主键关系。下一步就是拼UNION SELECT语句。

3. 访问控制粒度停留在“有无token”层面

没有角色区分,没有表级白名单,没有字段掩码。一个客服账号和DBA账号在MCP Server眼里毫无区别。

结果:攻击者用' OR 1=1 --触发报错,看到完整SQL模板;再构造SELECT * FROM users,拿到明文邮箱和密码哈希;最后用COPY users TO PROGRAM 'curl -X POST ...'外传全量数据。

根本原因解析:MCP协议配置不当的致命缺陷

MCP本身不背锅。问题出在开发者把MCP Server当成了“带认证的psql客户端”,忽略了它本质是数据库代理层

1. 权限管理被当成可选项

MCP规范明确要求Server实现permissions字段校验,但很多实现直接忽略或硬编码为["*"]。更危险的是,部分SDK自动生成的配置模板就默认开全权限。

2. 元数据接口设计即暴露面

/metadata不是调试后门,它是生产环境的高危端点。Supabase官方文档强调:“禁用元数据端点是生产部署的强制步骤”,但没人读。

3. 访问控制逻辑与数据库脱钩

MCP Server应该继承数据库的权限模型(比如PostgreSQL的role+schema+table三级权限),而不是另起一套粗粒度RBAC。常见错误是只校验token有效性,不校验该token对应的角色能否访问目标表。

4. 日志缺失导致攻击静默进行

所有SQL执行日志只写到stdout,没落盘、没告警、没采样。攻击者执行500次SELECT,运维系统零告警。

关键防御点:如何构建安全的MCP Server?

1. 最小权限原则必须落地

  • 删除所有["*"]配置,显式声明每个角色的权限:

    {
      "role": "analyst",
      "permissions": [
        {"table": "orders", "actions": ["SELECT"]},
        {"table": "products", "actions": ["SELECT"]}
      ]
    }
  • 禁用INSERT/UPDATE/DELETE权限,除非业务强依赖。读多写少场景下,90%的MCP调用只需SELECT
  • 定期用脚本扫描配置文件,自动告警未声明权限的表。

2. 访问控制必须绑定数据库原生能力

  • 不要自己实现表级ACL。让MCP Server调用PostgreSQL的has_table_privilege()函数校验:

    SELECT has_table_privilege('analyst_role', 'orders', 'SELECT');
  • 对敏感字段(如users.email, payments.card_number)启用动态列掩码,在SQL执行前重写查询:

    -- 原始请求
    SELECT id, email FROM users WHERE id = 123;
    
    -- 实际执行(对非admin角色)
    SELECT id, '***@***.com' AS email FROM users WHERE id = 123;

3. 输入校验不是防SQL注入的主力,而是最后一道保险

  • 参数化查询必须强制启用。禁止任何字符串拼接SQL:

    # ❌ 危险
    cursor.execute(f"SELECT * FROM {table} WHERE id = {user_input}")
    
    # ✅ 正确(使用pg8000或asyncpg的参数占位)
    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. 元数据接口必须物理隔离

  • 生产环境彻底删除/metadata路由。调试需求用本地supabase db remote schema pull替代。
  • 如果必须保留,加两道锁:

    • 只允许127.0.0.1和CI/CD服务器IP访问
    • 强制HTTP Basic Auth,凭证单独保管,不进Git

5. 日志必须包含可追溯的上下文

每条SQL执行日志至少记录:

  • 请求ID(用于链路追踪)
  • 用户角色(不是用户名,是实际生效的DB role)
  • 表名 + 字段数 + 扫描行数(EXPLAIN (FORMAT JSON)第一层)
  • 执行耗时(毫秒级)

示例日志:

[req-abc123] analyst@prod SELECT orders.id,orders.status FROM orders WHERE created_at > '2024-01-01' | rows=1240 | time=84ms

6. 安全审计不能靠自觉

  • 每次MCP Server发布前,运行mcp-audit工具(开源,见GitHub yitb/mcp-audit):

    • 检查配置中是否存在["*"]
    • 扫描代码中是否有cursor.execute(字符串拼接
    • 验证/metadata路由是否注册
  • 每季度请第三方做渗透测试,重点打/query端点的SQL注入和权限越界。

实战案例:如何应用上述原则构建安全的MCP Server?

下面是一个精简但生产可用的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. 参数化执行(假设用asyncpg)
    sql = f"SELECT {', '.join(query['columns'])} FROM {table}"
    if "where" in query:
        sql += f" WHERE {query['where']}"
    
    # 实际执行时用 $1, $2 占位符,此处省略参数绑定细节
    return {"result": run_sql(sql, query.get("params", []))}

关键点:

  • 权限检查在SQL生成前完成,不依赖数据库返回错误
  • 字段掩码在SQL拼装阶段介入,避免敏感字段进入查询
  • X-Role头由上游网关(如Traefik)根据JWT claim注入,不信任客户端传值

下一步行动:如何夯实你的MCP Server安全能力?

  • 立刻检查你的MCP Server配置:搜"permissions": \["\*\]"/metadata路由、cursor.execute(字符串拼接
  • 禁用元数据接口:删掉相关路由,或加IP+Basic Auth双锁
  • 部署日志采样:用Loki+Grafana监控/query端点的rows_scanned > 1000事件
  • 参考加固清单:yitb.com/mcp-hardening-checklist(开源,含Terraform检测脚本)

安全不是功能列表里的最后一项。它是MCP Server启动时第一个必须通过的健康检查。

返回首页