概述
目的和范围
本文档为Model Context Protocol (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指的是MCP服务器充当第三方API的客户端。它对于所有MCP服务器到第三方API的交互都是相同的值,无论哪个MCP客户端发起请求。易受攻击的条件
当所有以下条件都存在时,这种攻击成为可能:- MCP代理服务器使用静态客户端ID与第三方授权服务器
- MCP代理服务器允许MCP客户端动态注册(每个客户端获得自己的client_id)
- 第三方授权服务器在第一次授权后设置同意cookie
- MCP代理服务器在转发到第三方授权之前没有实施适当的每客户端同意
架构和攻击流程
Normal OAuth proxy usage (preserves user consent)
Malicious OAuth proxy usage (skips user consent)
攻击描述
当MCP代理服务器使用静态客户端ID与第三方授权服务器进行身份验证时,以下攻击成为可能:- 用户正常通过MCP代理服务器进行身份验证以访问第三方API
- 在此流程中,第三方授权服务器在用户代理上设置一个cookie,表示对静态客户端ID的同意
- 攻击者后来向用户发送一个恶意链接,其中包含一个精心设计的授权请求,该请求包含恶意重定向URI以及新的动态注册客户端ID
- 当用户点击链接时,他们的浏览器仍然有来自先前合法请求的同意cookie
- 第三方授权服务器检测到cookie并跳过同意屏幕
- MCP授权码被重定向到攻击者的服务器(在动态客户端注册期间在恶意
redirect_uri参数中指定) - 攻击者将窃取的授权码兑换为MCP服务器的访问令牌而无需用户的明确批准
- 攻击者现在可以作为受损用户访问第三方API
缓解措施
为了防止混淆代理攻击,MCP代理服务器必须实施每客户端同意和适当的安全控制,如下所述。同意流程实施
以下图表显示了如何正确实施在第三方授权流程之前运行的每客户端同意:必需的保护措施
每客户端同意存储 MCP代理服务器必须:- 维护每个用户的已批准
client_id值注册表 - 在启动第三方授权流程之前检查此注册表
- 安全地存储同意决定(服务器端数据库,或服务器特定cookie)
- 通过名称清楚地识别请求的MCP客户端
- 显示正在请求的特定第三方API范围
- 显示将发送令牌的注册
redirect_uri - 实施CSRF保护(例如,状态参数、CSRF令牌)
- 通过
frame-ancestorsCSP指令或X-Frame-Options: DENY防止iframe嵌入以防止点击劫持
- 对cookie名称使用
__Host-前缀 - 设置
Secure、HttpOnly和SameSite=Lax属性 - 进行加密签名或使用服务器端会话
- 绑定到特定的
client_id(不仅仅是”用户已同意”)
- 验证授权请求中的
redirect_uri与注册的URI完全匹配 - 如果
redirect_uri在没有重新注册的情况下发生变化,则拒绝请求 - 使用确切的字符串匹配(不是模式匹配或通配符)
state参数对于防止授权码拦截和CSRF攻击至关重要。适当的状态验证确保在授权端点的同意批准在回调端点得到强制执行。
实施OAuth流程的MCP代理服务器必须:
- 为每个授权请求生成加密安全的随机
state值 - 在同意被明确批准之后,仅在服务器端存储
state值(在安全的会话存储或加密cookie中) - 在重定向到第三方身份提供商之前立即设置
state跟踪cookie/会话(不是在同意批准之前) - 在回调端点验证
state查询参数与回调请求cookie或请求的基于cookie的会话中存储的值完全匹配 - 拒绝任何缺少
state参数或不匹配的回调请求 - 确保
state值是一次性的(验证后删除)并具有短暂的过期时间(例如,10分钟)
state值的同意cookie或会话不得在用户在MCP服务器的授权端点批准同意屏幕之后设置。在同意批准之前设置此cookie会使同意屏幕无效,因为攻击者可以通过制作恶意授权请求来绕过它。
令牌传递
“令牌传递”是一种反模式,其中MCP服务器接受来自MCP客户端的令牌,而不验证令牌是否正确发行_给MCP服务器_,并将它们传递给下游API。风险
令牌传递在授权规范中被明确禁止,因为它引入了许多安全风险,包括:- 安全控制规避
- MCP服务器或下游API可能实施重要的安全控制,如速率限制、请求验证或流量监控,这些控制依赖于令牌受众或其他凭据约束。如果客户端可以获得令牌并直接与下游API一起使用,而MCP服务器没有正确验证它们或确保令牌是为正确的服务发行的,它们就会绕过这些控制。
- 问责制和审计跟踪问题
- 当客户端使用上游发行的访问令牌(可能对MCP服务器不透明)调用时,MCP服务器将无法识别或区分MCP客户端。
- 下游资源服务器的日志可能显示似乎来自不同来源的请求,具有不同的身份,而不是实际转发令牌的MCP服务器。
- 这两个因素使事件调查、控制和审计更加困难。
- 如果MCP服务器在不验证其声明(例如,角色、权限或受众)或其他元数据的情况下传递令牌,拥有被盗令牌的恶意行为者可以使用服务器作为数据泄露的代理。
- 信任边界问题
- 下游资源服务器授予对特定实体的信任。此信任可能包括关于来源或客户端行为模式的假设。打破此信任边界可能导致意外问题。
- 如果令牌被多个服务接受而没有适当验证, compromising一个服务的攻击者可以使用令牌访问其他连接的服务。
- 未来兼容性风险
- 即使MCP服务器今天从”纯代理”开始,它可能稍后需要添加安全控制。从适当的令牌受众分离开始可以更容易地发展安全模型。
缓解措施
MCP服务器不得接受任何不是明确为MCP服务器发行的令牌。会话劫持
会话劫持是一种攻击向量,其中服务器向客户端提供会话ID,而未经授权的一方能够获得并使用相同的会话ID来冒充原始客户端并代表他们执行未经授权的操作。会话劫持提示注入
会话劫持冒充
攻击描述
当您有多个有状态的HTTP服务器处理MCP请求时,以下攻击向量成为可能: 会话劫持提示注入- 客户端连接到服务器A并接收会话ID。
-
攻击者获得现有会话ID并使用该会话ID向服务器B发送恶意事件。
- 当服务器支持重新传递/可恢复流时,在接收响应之前故意终止请求可能导致它通过服务器发送事件的GET请求被原始客户端恢复。
- 如果特定服务器作为工具调用的后果启动服务器发送事件,例如
notifications/tools/list_changed,其中可能影响服务器提供的工具,则客户端可能最终获得他们不知道已启用的工具。
- 服务器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客户端相同机器上执行的二进制文件。如果没有适当的沙盒化和同意要求,以下攻击成为可能:- 攻击者在客户端配置中包含恶意”启动”命令
- 攻击者在服务器本身中分发恶意负载
- 攻击者通过DNS重新绑定访问留在localhost上运行的不安全本地服务器
风险
来自不受信任来源或具有不充分限制的本地MCP服务器引入了几个关键安全风险:- 任意代码执行。攻击者可以使用MCP客户端权限执行任何命令。
- 无可见性。用户对正在执行的命令没有洞察力。
- 命令混淆。恶意行为者可以使用复杂或令人困惑的命令来显得合法。
- 数据泄露。攻击者可以通过妥协的javascript访问合法的本地MCP服务器。
- 数据丢失。攻击者或合法服务器中的bug可能导致主机机器上不可恢复的数据丢失。
缓解措施
如果MCP客户端支持一键本地MCP服务器配置,它必须在执行命令之前实施适当的同意机制。 预配置同意 在通过一键配置连接新本地MCP服务器之前显示清晰的同意对话框。MCP客户端必须:- 显示将要执行的确切命令,没有截断(包括参数和参数)
- 清楚地将其识别为在用户系统上执行代码的潜在危险操作
- 在继续之前需要明确的批准
- 允许用户取消配置
- 突出显示潜在危险的命令模式(例如,包含
sudo、rm -rf、网络操作、在预期目录之外的文件系统访问的命令) - 显示访问敏感位置的命令警告(主目录、SSH密钥、系统目录)
- 警告MCP服务器以与客户端相同的权限运行
- 在具有最小默认权限的沙盒环境中执行MCP服务器命令
- 使用受限的文件系统、网络和其他系统资源的访问权限启动MCP服务器
- 在需要时提供用户明确授予额外权限的机制(例如,特定目录访问、网络访问)
- 使用平台适当的沙盒技术(容器、chroot、应用程序沙盒等)
- 使用
stdio传输将访问限制为仅MCP客户端 - 如果使用HTTP传输,则限制访问,例如:
- 需要授权令牌
- 使用unix域套接字或其他进程间通信(IPC)机制,具有受限访问
范围最小化
糟糕的范围设计会增加令牌妥协的影响,提高用户摩擦,并模糊审计跟踪。攻击描述
攻击者通过日志泄露、内存抓取或本地拦截获得携带广泛范围(files:*、db:*、admin:*)的访问令牌,这些范围是预先授予的,因为MCP服务器在scopes_supported中公开了每个范围,而客户端请求了所有范围。该令牌启用横向数据访问、权限链接,以及在不重新同意整个表面情况下难以撤销。
风险
- 扩展爆炸半径:被盗的广泛令牌启用不相关的工具/资源访问
- 更高的撤销摩擦:撤销最大权限令牌会中断所有工作流程
- 审计噪音:单个综合范围掩盖每个操作的用户意图
- 权限链接:攻击者可以立即调用高风险工具而无需进一步提升提示
- 同意放弃:用户拒绝列出过多范围的对话框
- 范围膨胀盲目:缺乏指标使过度广泛的请求正常化
缓解措施
实施渐进式、最小权限范围模型:- 最小初始范围集(例如,
mcp:tools-basic)仅包含低风险发现/读取操作 - 通过针对性的
WWW-Authenticatescope="..."挑战在首次尝试特权操作时进行增量提升 - 向下范围容忍:服务器应该接受减少范围的令牌;授权服务器可能发出请求范围的子集
- 发出精确的范围挑战;避免返回完整目录
- 使用相关ID记录提升事件(请求的范围、授予的子集)
- 仅以基线范围开始(或由初始
WWW-Authenticate指定的范围) - 缓存最近失败以避免对拒绝范围的重复提升循环
常见错误
- 在
scopes_supported中发布所有可能的范围 - 使用通配符或综合范围(
*、all、full-access) - 捆绑不相关的权限以预先防止未来的提示
- 在每次挑战中返回整个范围目录
- 在没有版本控制的情况下静默更改范围语义
- 将令牌中声明的范围视为足够的,而没有服务器端授权逻辑