简介
目的与范围
本文档提供了模型上下文协议 (MCP) 的安全注意事项,作为 MCP 授权 规范的补充。本文档识别了针对 MCP 实现的安全风险、攻击向量和最佳实践。 本文档的主要受众包括实现 MCP 授权流程的开发人员、MCP 服务器运营商,以及评估基于 MCP 系统的安全专业人员。阅读本文档时,应结合 MCP 授权规范和 OAuth 2.0 安全最佳实践。攻击与缓解措施
本节详细描述了对 MCP 实现的攻击以及潜在的对策。混淆代理问题
攻击者可以利用连接到第三方 API 的 MCP 代理服务器,制造“混淆代理”漏洞。这种攻击利用静态客户端 ID、动态客户端注册和授权 Cookie 的组合,允许恶意客户端在未经用户妥善同意的情况下获取授权码。术语
MCP 代理服务器:一种将 MCP 客户端连接到第三方 API 的 MCP 服务器,在提供 MCP 功能的同时委托操作,并作为第三方 API 服务器的单一 OAuth 客户端。 第三方授权服务器:保护第三方 API 的授权服务器。它可能缺乏动态客户端注册支持,要求 MCP 代理对所有请求使用静态客户端 ID。 第三方 API:提供实际 API 功能的受保护资源服务器。访问此 API 需要第三方授权服务器颁发的令牌。 静态客户端 ID:MCP 代理服务器在与第三方授权服务器通信时使用的固定 OAuth 2.0 客户端标识符。该客户端 ID 指代作为第三方 API 客户端的 MCP 服务器。无论由哪个 MCP 客户端发起请求,所有 MCP 服务器与第三方 API 的交互均使用相同的值。易受攻击的条件
当满足以下所有条件时,此类攻击将成为可能:- MCP 代理服务器在第三方授权服务器上使用 静态客户端 ID
- MCP 代理服务器允许 MCP 客户端 动态注册(每个客户端获得自己的 client_id)
- 第三方授权服务器在首次授权后设置 授权 Cookie (consent cookie)
- MCP 代理服务器在转发至第三方授权前未实现适当的针对每个客户端的征得同意机制
架构与攻击流程
正常 OAuth 代理使用(保留用户同意)
恶意 OAuth 代理使用(跳过用户同意)
攻击描述
当 MCP 代理服务器使用静态客户端 ID 向第三方授权服务器进行身份验证时,以下攻击成为可能:- 用户通过 MCP 代理服务器进行正常身份验证以访问第三方 API
- 在此流程中,第三方授权服务器在用户代理(浏览器)上设置一个 Cookie,表示已对该静态客户端 ID 授权
- 攻击者随后向用户发送一个恶意链接,其中包含一个精心构造的授权请求,该请求包含恶意的重定向 URI 以及一个新的动态注册客户端 ID
- 当用户点击链接时,其浏览器中仍保留有先前合法请求的授权 Cookie
- 第三方授权服务器检测到该 Cookie 并跳过授权确认界面
- MCP 授权码被重定向到攻击者的服务器(即在 动态客户端注册 期间由恶意
redirect_uri参数指定的地址) - 攻击者在未经用户显式批准的情况下,将窃取的授权码交换为 MCP 服务器的访问令牌
- 攻击者现在可以以受害用户的身份访问第三方 API
缓解措施
为防止混淆代理攻击,MCP 代理服务器 必须 实现针对每个客户端的征得同意机制及下文详述的适当安全控制。授权流程实现
下图展示了如何正确实现 在 第三方授权流程之前运行的针对每个客户端的征得同意机制:必要的防护措施
针对客户端的同意存储 MCP 代理服务器 必须:- 维护一个按用户记录的已批准
client_id值的注册表 - 在启动第三方授权流程 之前 检查此注册表
- 安全地存储授权决策(服务器端数据库或服务器特定 Cookie)
- 清晰地按名称识别发起请求的 MCP 客户端
- 显示正在请求的具体第三方 API 权限范围 (scopes)
- 显示将发送令牌的已注册
redirect_uri - 实现 CSRF 防护(例如 state 参数、CSRF 令牌)
- 通过
frame-ancestorsCSP 指令或X-Frame-Options: DENY防止被嵌入 iframe,以防止点击劫持
- 对 Cookie 名称使用
__Host-前缀 - 设置
Secure、HttpOnly和SameSite=Lax属性 - 经过加密签名或使用服务器端会话
- 绑定到特定的
client_id(而不仅仅是“用户已同意”)
- 验证授权请求中的
redirect_uri与注册的 URI 完全匹配 - 如果
redirect_uri在未重新注册的情况下发生更改,则拒绝请求 - 使用精确字符串匹配(而非模式匹配或通配符)
state 参数对于防止授权码拦截和 CSRF 攻击至关重要。正确的 state 验证可确保授权端点的许可在回调端点得到强制执行。 实现 OAuth 流程的 MCP 代理服务器 必须:- 为每个授权请求生成一个加密安全的随机
state值 - 仅在 授权被明确批准后,将
state值存储在服务器端(安全会话存储或加密 Cookie 中) - 在重定向到第三方身份提供者 紧前 设置
state跟踪 Cookie/会话(而非在批准授权前) - 在回调端点验证
state查询参数与回调请求的 Cookie 或基于 Cookie 的会话中存储的值完全匹配 - 拒绝任何
state参数缺失或不匹配的回调请求 - 确保
state值是一次性的(验证后删除)且过期时间短(例如 10 分钟)
state 值的授权 Cookie 或会话 绝不能 在用户于 MCP 服务器授权端点批准授权界面 之前 被设置。在批准前设置此 Cookie 会导致授权界面失效,因为攻击者可以通过构造恶意授权请求来绕过它。
令牌透传
“令牌透传 (Token passthrough)”是一种反模式,即 MCP 服务器接受来自 MCP 客户端的令牌,但未验证该令牌是否是正确颁发 给该 MCP 服务器 的,便将其透传给下游 API。风险
令牌透传在 授权规范 中被明确禁止,因为它会引入一系列安全风险,包括:- 绕过安全控制
- MCP 服务器或下游 API 可能会实现重要的安全控制措施,如速率限制、请求验证或流量监控,这些措施依赖于令牌受众 (audience) 或其他凭据约束。如果客户端可以绕过 MCP 服务器的验证,直接使用令牌访问下游 API,或确保令牌是为正确的服务颁发的,这些控制措施就会失效。
- 问责制和审计追踪问题
- 当客户端使用对 MCP 服务器可能是不透明的(由上游颁发的)访问令牌进行调用时,MCP 服务器将无法识别或区分不同的 MCP 客户端。
- 下游资源服务器的日志可能显示请求似乎来自不同的源或具有不同的身份,而不是实际转发令牌的 MCP 服务器。
- 这两个因素都会使事件调查、控制和审计变得更加困难。
- 如果 MCP 服务器在不验证令牌声明(例如角色、权限或受众)或其他元数据的情况下透传令牌,持有被盗令牌的恶意行为者可以利用该服务器作为数据外泄的代理。
- 信任边界问题
- 下游资源服务器授予特定实体信任。这种信任可能包含关于来源或客户端行为模式的假设。打破这一信任边界可能导致预料之外的问题。
- 如果令牌在没有妥善验证的情况下被多个服务接受,攻击者一旦攻破其中一个服务,就可以使用该令牌访问其他连接的服务。
- 未来兼容性风险
- 即使 MCP 服务器目前作为“纯代理”启动,未来也可能需要添加安全控制。从一开始就保持适当的令牌受众隔离,更有利于安全模型的演进。
缓解措施
MCP 服务器 绝不能 接受任何非显式颁发给该 MCP 服务器的令牌。会话劫持
会话劫持是一种攻击向量,服务器向客户端提供会话 ID,而未经授权的第三方能够获取并使用该会话 ID 来冒充原始客户端,并代表其执行未经授权的操作。会话劫持提示词注入
会话劫持冒充
攻击描述
当您有多个处理 MCP 请求的有状态 HTTP 服务器时,可能存在以下攻击向量: 会话劫持提示词注入- 客户端连接到 服务器 A 并接收一个会话 ID。
-
攻击者获得现有的会话 ID,并向 服务器 B 发送带有该会话 ID 的恶意事件。
- 当服务器支持 重新交付/可恢复流 时,故意在收到响应前终止请求,可能导致原始客户端通过服务器发送事件 (SSE) 的 GET 请求恢复该响应。
- 如果特定服务器因工具调用(如
notifications/tools/list_changed)而启动 SSE,且可以影响服务器提供的工具,客户端最终可能会获得一些他们并未意识到已启用的工具。
- 服务器 B 将事件(与会话 ID 关联)排入共享队列。
- 服务器 A 使用会话 ID 从队列中轮询事件并检索恶意有效载荷。
- 服务器 A 将恶意有效载荷作为异步或恢复的响应发送给客户端。
- 客户端接收并执行恶意有效载荷,导致潜在的系统受损。
- MCP 客户端与 MCP 服务器进行身份验证,创建一个持久会话 ID。
- 攻击者获取该会话 ID。
- 攻击者使用该会话 ID 向 MCP 服务器发起调用。
- MCP 服务器不检查额外的授权,将攻击者视为合法用户,从而允许未经授权的访问或操作。
缓解措施
为了防止会话劫持和事件注入攻击,应实施以下缓解措施: 实现授权的 MCP 服务器 必须 验证所有入站请求。MCP 服务器 绝不能 仅使用会话进行身份验证。 MCP 服务器 必须 使用安全的、非确定性的会话 ID。生成的会话 ID(例如 UUID)应当 使用安全随机数生成器。避免使用可预测或顺序的、可能被攻击者猜测的会话标识符。轮换或使会话 ID 过期也可以降低风险。 MCP 服务器 应当 将会话 ID 与用户特定信息绑定。在存储或传输会话相关数据(例如在队列中)时,将会话 ID 与授权用户的唯一信息(如内部用户 ID)结合。使用类似<user_id>:<session_id> 的键格式。这确保了即使攻击者猜到了会话 ID,也无法冒充其他用户,因为用户 ID 是从用户令牌派生的,而非由客户端提供。 MCP 服务器还可以选择利用额外的唯一标识符。本地 MCP 服务器受损
本地 MCP 服务器是在用户本地机器上运行的 MCP 服务器,来源包括用户下载并执行、用户自行编写,或通过客户端的配置流程安装。这些服务器可能直接访问用户的系统,并可能被用户机器上运行的其他进程访问,使其成为极具吸引力的攻击目标。攻击描述
本地 MCP 服务器是在与 MCP 客户端相同的机器上下载并执行的二进制文件。如果没有适当的沙箱机制和征得同意的要求,以下攻击将成为可能:- 攻击者在客户端配置中包含恶意的“启动 (startup)”命令
- 攻击者在服务器内部散布恶意有效载荷
- 攻击者通过 DNS 重绑定访问在 localhost 上运行的不安全本地服务器
风险
缺乏足够限制或来自不可信来源的本地 MCP 服务器会引入若干关键安全风险:- 任意代码执行。攻击者可以以 MCP 客户端的权限执行任何命令。
- 缺乏可见性。用户无法察觉正在执行哪些命令。
- 命令混淆。恶意行为者可以使用复杂或晦涩的命令使其看起来合法。
- 数据外泄。攻击者可以通过受损的 Javascript 访问合法的本地 MCP 服务器。
- 数据丢失。攻击者或合法服务器中的漏洞可能导致宿主机上不可恢复的数据丢失。
缓解措施
如果 MCP 客户端支持一键式本地 MCP 服务器配置,它 必须 在执行命令前实现适当的征得同意机制。 预配置同意 在通过一键配置连接新的本地 MCP 服务器之前,显示清晰的授权对话框。MCP 客户端 必须:- 完整显示将要执行的命令,不得截断(包括参数和变量)
- 明确将其标识为在用户系统上执行代码的潜在危险操作
- 在继续操作前要求用户显式批准
- 允许用户取消配置
- 突出显示潜在危险的命令模式(例如包含
sudo、rm -rf、网络操作、预期目录之外的文件系统访问的命令) - 对访问敏感位置(主目录、SSH 密钥、系统目录)的命令显示警告
- 警告 MCP 服务器以与客户端相同的权限运行
- 在具有最小默认权限的沙箱环境中执行 MCP 服务器命令
- 启动 MCP 服务器时,限制其访问文件系统、网络和其他系统资源
- 提供机制,允许用户在需要时显式授予额外权限(例如特定目录访问权限、网络访问权限)
- 使用平台适用的沙箱技术(容器、chroot、应用沙箱等)
- 使用
stdio传输,将访问限制为仅限 MCP 客户端 - 如果使用 HTTP 传输,则限制访问,例如:
- 要求授权令牌
- 使用 Unix 域套接字或其他具有访问限制的进程间通信 (IPC) 机制
权限范围最小化
糟糕的权限范围 (scope) 设计会增加令牌受损的影响,提高用户阻力,并使审计追踪变得模糊。攻击描述
攻击者获取了(通过日志泄露、内存抓取或本地拦截)带有广泛权限范围(files:*, db:*, admin:*)的访问令牌。这些权限是在最初预先授予的,因为 MCP 服务器暴露了 scopes_supported 中的所有范围,而客户端请求了所有范围。该令牌实现了横向数据访问、权限链攻击,并且在不重新对整个层面征得同意的情况下难以撤销。
风险
- 爆炸半径扩大:被盗的广义令牌可访问不相关的工具/资源
- 撤销阻力更高:撤销最高权限令牌会中断所有工作流
- 审计噪音:单一的概括性权限掩盖了每次操作的用户真实意图
- 权限链:攻击者可以立即调用高风险工具,而无需进一步的提权提示
- 放弃授权:用户会拒绝列出过多权限范围的对话框
- 权限膨胀盲区:缺乏指标使得过度广泛的请求变得常态化
缓解措施
实现渐进式的最小权限范围模型:- 最小初始权限集(例如
mcp:tools-basic),仅包含低风险的发现/读取操作 - 首次尝试提权操作时,通过针对性的
WWW-Authenticatescope="..."质询进行增量提权 - 缩减权限容差:服务器应接受权限范围缩减的令牌;授权服务器“可以”仅颁发所请求权限的子集
- 发出精确的权限质询;避免返回完整的权限目录
- 记录提权事件(请求的范围、授予的子集)并附带关联 ID
- 仅从基准权限(或由初始
WWW-Authenticate指定的权限)开始 - 缓存最近的失败记录,以避免对已拒绝的权限范围进行重复提权循环
常见错误
- 在
scopes_supported中发布所有可能的权限范围 - 使用通配符或概括性范围(
*,all,full-access) - 捆绑不相关的权限以规避未来的提示
- 在每次质询中返回整个权限目录
- 在未进行版本控制的情况下静默更改权限语义
- 将令牌中声明的权限视为已足够,而缺乏服务器端授权逻辑