🧩 MCP生态

MCP Server插件权限漏洞分析:修复未认证访问导致的数据库泄露风险

发布时间:2026-04-14 分类: MCP生态
摘要:MCP Server插件权限漏洞剖析:MCP协议安全边界实战笔记一、真实问题现场某MCP Server插件上线后不久,客户反馈数据库被全量导出。日志显示,一个未认证的HTTP请求调用了/tools/list_tools端点,返回结果里赫然包含users、payments、auth_sessions等表名,以及字段定义和索引信息。攻击者据此构造SQL注入载荷,绕过应用层直接读取敏感数据。这不是...

封面

MCP Server插件权限漏洞剖析:MCP协议安全边界实战笔记

一、真实问题现场

某MCP Server插件上线后不久,客户反馈数据库被全量导出。日志显示,一个未认证的HTTP请求调用了/tools/list_tools端点,返回结果里赫然包含userspaymentsauth_sessions等表名,以及字段定义和索引信息。攻击者据此构造SQL注入载荷,绕过应用层直接读取敏感数据。

这不是理论风险——是已发生的生产事故。

二、漏洞根因拆解

权限校验完全缺失

插件收到MCP请求时,直接执行工具逻辑,未检查request.context.permissions字段。MCP协议要求Server在调用任何工具前验证权限上下文,但该插件把permissions当成了可选字段处理。

后果很直接:任意客户端(包括curl、Postman)都能触发list_tools,而这个工具又没做输入过滤或输出脱敏。

mcp.tools.list_tools暴露结构信息

该工具本意是供Agent运行时动态发现可用能力,但插件实现中:

  • 直接返回了数据库连接池的元数据(pg_catalog查询结果)
  • 未剥离information_schema中的敏感字段(如column_default含密码哈希逻辑)
  • 返回体未做角色过滤——管理员和访客看到的工具列表完全一致

元数据不是“辅助信息”,它是攻击链的第一块垫脚石。

RBAC形同虚设

插件声明支持RBAC,但实际只做了两件事:

  • 在配置文件里写了"role": "admin"硬编码
  • 所有工具函数都忽略context.role参数

结果:普通用户调用delete_user时,代码路径和管理员完全相同。

三、Supabase场景下的放大效应

当MCP Server对接Supabase时,问题更致命。Supabase默认开启Row Level Security(RLS),但前提是客户端必须提供有效JWT。而该插件:

  • 用服务端私钥直连Supabase,绕过RLS
  • list_tools返回的表名直接映射到Supabase的公开视图名
  • 攻击者拿到auth.users表结构后,立刻用https://your-project.supabase.co/rest/v1/auth.users?select=*发起跨域请求

Supabase没破防,是MCP Server把门钥匙交给了路人。

四、守住MCP安全边界的实操要点

协议规范的关键约束

MCP协议对权限的强制要求就三条,必须落地:

  • 所有工具调用前,必须校验context.permissions是否包含当前操作所需scope
  • list_tools返回的工具列表,必须按context.role动态过滤(例如user角色看不到db.dump
  • context对象不可被客户端篡改——Server需用签名验证其完整性

Server开发必须做的三件事

  1. 权限校验前置
    在路由层统一拦截,拒绝permissions为空或缺失必要scope的请求:

    def require_permission(scope: str):
        if not request.context.permissions or scope not in request.context.permissions:
            raise PermissionError(f"Missing permission: {scope}")
  2. 工具元数据严格分级
    list_tools返回值按角色隔离:

    • guest: 仅返回weather.getcalculator.add
    • user: 增加db.query(但隐藏table参数枚举)
    • admin: 才返回db.dumpsystem.exec
  3. 禁用危险反射
    禁止工具函数通过getattr()动态调用数据库方法。所有DB操作必须显式声明依赖的表和字段,并在启动时注册白名单:

    # 正确:白名单驱动
    DB_OPERATIONS = {
        "users.read": {"tables": ["users"], "fields": ["id", "email"]},
        "payments.write": {"tables": ["payments"], "fields": ["amount", "status"]}
    }

商业化案例中的硬性红线

医疗AI Agent的MCP Server部署时,我们强制执行:

  • 所有数据库操作必须走预编译语句(PreparedStatement),禁止拼接SQL
  • context.permissions由OIDC ID Token解码生成,不接受客户端传入的任何权限字段
  • 每次list_tools调用触发审计日志,记录client_ipuser_roletimestamp
  • 敏感工具(如db.export)需二次确认,且导出文件自动加密+72小时过期

这些不是“最佳实践”,是HIPAA合规的底线。

五、yitb.com的定位

yitb.com不做MCP协议翻译器,只做三件事:

  • 把协议规范里的安全条款,转换成可测试的代码检查清单(比如“list_tools响应体必须包含role字段”)
  • 公开真实漏洞的复现环境和修复diff(含Supabase、Postgres、MongoDB三套方案)
  • 拆解商业化Agent的权限模型设计——从医疗到金融,每份架构图都标注了MCP协议如何嵌入现有IAM体系

六、立刻能做的四件事

  1. 检查你的list_tools实现
    用curl发个无权限请求:curl -X POST http://localhost:3000/tools/list_tools,看返回里有没有数据库表名或系统路径。
  2. 给所有工具加权限守卫
    在每个工具函数开头插入:

    assert "db.read" in context.permissions, "Permission denied"
  3. 删掉所有eval()exec()getattr(..., user_input)
    MCP工具必须是纯函数,输入输出完全可预测。
  4. 用Supabase RLS做最后一道闸
    即使MCP Server出问题,也让数据库自己拦住非法查询:

    ALTER TABLE users ENABLE ROW LEVEL SECURITY;
    CREATE POLICY select_own ON users FOR SELECT USING (auth.uid() = id);
返回首页