介绍
目的和范围
本文档为模型上下文协议(MCP)提供安全性注意事项,与 MCP 授权 规范相辅相成。本文档识别了特定于 MCP 实现的安全风险、攻击向量和最佳实践。 本文档的主要受众包括实现 MCP 授权流程的开发者、MCP 服务器运营者以及评估基于 MCP 系统的安全专业人员。本文档应与 MCP 授权规范以及 OAuth 2.0 安全最佳实践 一起阅读。攻击与缓解措施
本节详细描述了针对 MCP 实现的攻击,以及潜在的应对措施。混淆代理问题
攻击者可以利用连接到第三方 API 的 MCP 代理服务器,制造 “混淆代理” 漏洞。此攻击允许恶意客户端通过利用 静态客户端 ID、动态客户端注册以及 同意 cookie 的组合,在没有适当用户同意的情况下获取 授权码。术语
MCP 代理服务器 : 一种 MCP 服务器,它将 MCP 客户端连接到第三方 API,提供 MCP 功能,同时委托操作,并作为单一 OAuth 客户端与第三方 API 服务器交互。 第三方授权服务器 : 保护第三方 API 的授权服务器。它可能不支持 动态客户端注册,因此要求 MCP 代理为所有请求使用 静态客户端 ID。 第三方 API : 提供实际 API 功能的受保护资源服务器。访问此 API 需要由 第三方授权服务器颁发的令牌。 静态客户端 ID : MCP 代理服务器在与 第三方授权服务器通信时使用的固定 OAuth 2.0 客户端标识符。此 Client ID 指的是作为客户端访问第三方 API 的 MCP 服务器。对于所有 MCP 服务器到第三方 API 的交互,它都是相同的值, 无论是哪个 MCP 客户端发起的请求。易受攻击的条件
当同时满足以下所有条件时, 此攻击才可能发生:- MCP 代理服务器与第三方 授权服务器使用 静态客户端 ID
- MCP 代理服务器允许 MCP 客户端 动态注册(每个 客户端都有自己的 client_id)
- 第三方授权服务器在首次授权后设置 同意 cookie
- MCP 代理服务器在转发到第三方授权之前没有实施正确的按客户端同意机制
架构与攻击流程
正常的 OAuth 代理使用(保留用户同意)
恶意的 OAuth 代理使用(跳过用户同意)
攻击描述
当 MCP 代理服务器使用静态客户端 ID 与 第三方授权服务器进行身份验证时,以下攻击成为 可能:- 用户通过 MCP 代理服务器正常进行身份验证,以访问 第三方 API
- 在此流程中,第三方授权服务器会在用户代理上设置一个 cookie, 表明静态客户端 ID 已获得同意
- 攻击者随后向用户发送一个恶意链接,其中包含一个 精心构造的授权请求,该请求包含恶意重定向 URI,外加一个新注册的动态客户端 ID
- 当用户点击该链接时,其浏览器仍然保留着 上一次合法请求留下的同意 cookie
- 第三方授权服务器检测到该 cookie,并跳过 同意页面
- MCP 授权码被重定向到攻击者的服务器
(在恶意
redirect_uri参数中指定,位于 动态客户端注册 期间) - 攻击者将窃取到的授权码兑换为 MCP 服务器的访问 令牌,而无需用户的明确批准
- 攻击者现在可以作为被攻陷的 用户访问第三方 API
缓解措施
为防止混淆代理攻击,MCP 代理服务器 MUST 按如下所述实施 按客户端同意和适当的安全控制。同意流程实现
下图展示了如何在第三方授权流程 之前正确实现按客户端同意:所需防护措施
按客户端存储同意 MCP 代理服务器 MUST:- 为每个用户维护已批准
client_id值的注册表 - 在启动第三方 授权流程之前检查此注册表
- 安全地存储同意决定(服务器端数据库,或特定于服务器的 cookie)
- 清楚标识请求的 MCP 客户端名称
- 显示正在请求的具体第三方 API 范围
- 显示将接收令牌的已注册
redirect_uri - 实施 CSRF 防护(例如 state 参数、CSRF 令牌)
- 通过
frame-ancestorsCSP 指令或X-Frame-Options: DENY阻止被嵌入,以防点击劫持
- 对 cookie 名称使用
__Host-前缀 - 设置
Secure、HttpOnly和SameSite=Lax属性 - 进行加密签名或使用服务器端会话
- 绑定到特定的
client_id(而不仅仅是“用户已同意”)
- 验证授权请求中的
redirect_uri是否与已注册 URI 完全一致 - 如果
redirect_uri在未重新注册的情况下发生变化,则拒绝请求 - 使用精确字符串匹配(而不是模式匹配或通配符)
state 参数对于防止授权码
拦截和 CSRF 攻击至关重要。正确的 state 验证可确保
授权端点上的同意批准在回调端点得到强制执行。
实现 OAuth 流程的 MCP 代理服务器 MUST:
- 为每个授权请求生成一个密码学安全的随机
state值 - 仅在同意已被明确批准后,才将
state值存储在服务器端(安全会话存储或加密 cookie 中) - 在重定向到第三方身份提供者之前(而不是在同意批准之前),立即设置
state跟踪 cookie/会话 - 在回调端点验证
state查询参数是否与回调请求的 cookie 或请求的基于 cookie 的会话中的存储值完全匹配 - 拒绝任何缺少
state参数或不匹配的回调请求 - 确保
state值只能使用一次(验证后删除)并具有较短的过期时间(例如 10 分钟)
state 值的同意 cookie 或会话。在同意批准之前设置此 cookie 会使同意页面失去效力,因为攻击者可以通过精心构造恶意授权请求来绕过它。
令牌透传
“令牌透传”是一种反模式,指 MCP 服务器在未验证令牌是否已正确颁发给 MCP 服务器 的情况下,从 MCP 客户端接收令牌,并将其透传给下游 API。风险
根据授权规范,“令牌透传”是明确禁止的,因为它会带来多种安全风险,包括:- 安全控制规避
- MCP 服务器或下游 API 可能实现了重要的安全控制,例如速率限制、请求验证或流量监控,这些控制依赖于令牌受众或其他凭证约束。如果客户端能够不经过 MCP 服务器正确验证,或不确保令牌是为正确服务颁发的,就直接与下游 API 获取并使用令牌,它们就会绕过这些控制。
- 可追溯性与审计轨迹问题
- 当客户端使用的是上游颁发的访问令牌,而该令牌对 MCP 服务器来说可能是不可读的,MCP 服务器将无法识别或区分不同的 MCP 客户端。
- 下游资源服务器的日志可能会显示来自不同来源、具有不同身份的请求,而不是实际在转发令牌的 MCP 服务器。
- 这两个因素都会使事件调查、控制和审计更加困难。
- 如果 MCP 服务器在未验证令牌声明(例如角色、权限或受众)或其他元数据的情况下透传令牌,那么持有被盗令牌的恶意行为者就可以将该服务器用作数据外传的代理。
- 信任边界问题
- 下游资源服务器会对特定实体授予信任。这种信任可能包括对来源或客户端行为模式的假设。破坏这一信任边界可能导致意外问题。
- 如果某个令牌在多个服务中都能被接受,而没有经过适当验证,那么攻击者一旦攻破其中一个服务,就可以使用该令牌访问其他已连接的服务。
- 未来兼容性风险
- 即使某个 MCP 服务器今天只是一个“纯代理”,它将来仍可能需要添加安全控制。从一开始就做好令牌受众隔离,会让安全模型的演进更容易。
缓解措施
MCP 服务器不得接受任何未明确为 MCP 服务器颁发的令牌。服务端请求伪造(SSRF)
服务端请求伪造(SSRF)是一种攻击,攻击者可以诱使 MCP 客户端向非预期的目标发起 HTTP 请求,从而可能访问内部网络资源、云元数据端点或其他受保护的服务。攻击描述
在 OAuth 元数据发现过程中,MCP 客户端会从多个可能被恶意 MCP 服务器控制的来源获取 URL:- 来自
WWW-Authenticate响应头的resource_metadataURL - 来自受保护资源元数据文档的
authorization_serversURLs - 来自授权服务器元数据的
token_endpoint、authorization_endpoint和其他 URLs
- 直接访问内部 IP:如
http://192.168.1.1/admin或http://10.0.0.1/api这类 URL 目标指向内部网络服务 - 云元数据端点:指向
http://169.254.169.254/(AWS/GCP/Azure 元数据服务)的 URL 可能泄露云凭据和实例信息 - 本地主机服务:如
http://localhost:6379/这类 URL 可与本地服务交互(Redis、数据库、管理面板) - DNS 重新绑定:在验证和使用之间更改 DNS 解析的域名(例如,
https://attacker.com最初解析到安全的 IP,随后解析到192.168.1.1) - 重定向链:看起来正常的 URL,实际会重定向到内部资源
风险
- 凭据泄露:云元数据端点通常会暴露 IAM 凭据、API 密钥和其他机密
- 内部网络侦察:错误消息会泄露内部网络拓扑和服务的信息
- 服务交互:POST 请求(例如发送到 token 端点)可能触发对内部服务的变更
- 防火墙绕过:MCP 客户端充当代理,绕过网络边界控制
- 数据泄露:内部服务响应可能通过错误消息或 OAuth 流程回传给攻击者
缓解措施
部署到服务器上的 MCP 客户端 必须 考虑 SSRF 风险,并在获取 OAuth 相关 URL 时实施适当的缓解措施。适用哪些防护措施取决于你的网络环境。 强制使用 HTTPS MCP 客户端在生产环境中 应当 对所有 OAuth 相关 URL 要求使用 HTTPS:- 拒绝
http://URL,但开发期间允许回环地址(localhost、127.0.0.1、::1)例外 - 这与 OAuth 2.1 第 1.5 节 保持一致,该节要求除回环重定向 URI 外,所有 OAuth 协议 URL 都必须使用 HTTPS
- 为开发/测试场景提供明确的退出机制
- IPv4 私有地址段:
10.0.0.0/8、172.16.0.0/12、192.168.0.0/16 - 回环地址:
127.0.0.0/8、::1(除非在开发场景中明确允许) - 链路本地地址:
169.254.0.0/16(包括云元数据端点) - IPv6 私有地址段:
fc00::/7、fe80::/10
避免手动实现 IP 校验。攻击者会利用编码技巧(八进制、十六进制、IPv4 映射 IPv6)绕过自定义解析器,而这些技巧往往会被忽略。
- 不要盲目跟随重定向到内部资源
- 对重定向目标应用 HTTPS 和 IP 段限制
- 考虑禁用自动跟随重定向,并逐跳验证每一次跳转
- 让 OAuth 发现请求通过代理转发,并阻止访问内部目的地
- 使用诸如 Smokescreen 或类似的出口代理,它们从设计上防止 SSRF
- 配置网络策略,限制 MCP 客户端的出站访问
- 攻击者的域名在验证时可能解析到安全的 IP,但在实际请求时解析到内部 IP
- 考虑在检查与使用之间固定 DNS 解析结果
- 分层防御:将 DNS 检查与其他缓解措施结合使用
资源与工具
以下资源可以帮助开发者在 MCP 客户端中实现 SSRF 防护。 参考文档- OWASP SSRF Prevention Cheat Sheet: 关于 SSRF 防护技术的综合指南,包括输入验证、允许列表策略和网络级控制
- OWASP Top 10 A10:2021 - SSRF: 最关键 Web 应用安全风险背景下的 SSRF 说明
会话劫持
会话劫持是一种攻击向量,其中服务器向客户端提供一个会话 ID,而未授权方能够获取并使用相同的会话 ID 来冒充原始客户端,并代表其执行未授权操作。会话劫持提示注入
会话劫持冒充
攻击描述
当你有多个处理 MCP 请求的有状态 HTTP 服务器时,可能存在以下攻击向量: 会话劫持提示注入- 客户端连接到 Server A 并接收一个会话 ID。
-
攻击者获取一个现有会话 ID,并使用该会话 ID 向 Server B 发送恶意事件。
- 如果某个服务器会因工具调用(例如
notifications/tools/list_changed)而发起服务器发送事件,并且这会影响服务器提供的工具,那么客户端最终可能会接收到其并不知道已启用的工具。
- 如果某个服务器会因工具调用(例如
- Server B 将该事件(与会话 ID 关联)入队到共享队列中。
- Server A 使用会话 ID 轮询队列中的事件,并检索到恶意载荷。
- Server A 将恶意载荷作为异步或恢复的响应发送给客户端。
- 客户端接收并执行恶意载荷,从而可能导致被攻破。
- MCP 客户端向 MCP 服务器进行认证,创建一个持久会话 ID。
- 攻击者获取该会话 ID。
- 攻击者使用该会话 ID 向 MCP 服务器发起调用。
- MCP 服务器不会检查额外授权,并将攻击者视为合法用户,从而允许未授权访问或操作。
缓解措施
为防止会话劫持和事件注入攻击,应实施以下缓解措施: 实现授权的 MCP 服务器 必须 验证所有传入请求。MCP 服务器 不得 使用会话进行身份验证。 MCP 服务器 必须 使用安全的、非确定性的会话 ID。生成的会话 ID(例如 UUID)应当 使用安全随机数生成器。避免使用可预测或连续递增的会话标识符,因为攻击者可能猜测出来。轮换或过期会话 ID 也可以降低风险。 MCP 服务器 应当 将会话 ID 与用户特定信息绑定。在存储或传输与会话相关的数据时(例如在队列中),将会话 ID 与授权用户唯一的信息结合起来,例如其内部用户 ID。使用类似<user_id>:<session_id> 的键格式。这样即使攻击者猜到了会话 ID,也无法冒充其他用户,因为 user ID 是从用户令牌派生而来,而不是由客户端提供的。
MCP 服务器可以选择性地利用额外的唯一标识符。
本地 MCP 服务器被攻破
本地 MCP 服务器是在用户本地机器上运行的 MCP 服务器, 可以是用户下载并执行的服务器、用户自己编写的服务器, 或者通过客户端的配置流程安装的服务器。 这些服务器可能直接访问用户的系统,也可能被用户机器上运行的其他进程访问, 因此成为攻击的有吸引力目标。攻击描述
本地 MCP 服务器是与 MCP 客户端运行在同一台机器上并被下载和执行的二进制文件。 如果没有适当的沙箱隔离和同意要求,以下攻击就会成为可能:- 攻击者在客户端配置中包含一个恶意的“启动”命令
- 攻击者在服务器本身中分发恶意载荷
- 攻击者通过 DNS rebinding 访问一个一直运行在 localhost 上的不安全本地服务器
风险
来自不受信任来源或限制不足的本地 MCP 服务器会带来若干严重的安全风险:- 任意代码执行。攻击者可以使用 MCP 客户端权限执行任意命令。
- 缺乏可见性。用户无法了解正在执行哪些命令。
- 命令混淆。恶意行为者可以使用复杂或曲折的命令来伪装成合法操作。
- 数据外泄。攻击者可以通过被攻破的 JavaScript 访问合法的本地 MCP 服务器。
- 数据丢失。攻击者或合法服务器中的漏洞都可能导致主机上的数据不可恢复地丢失。
缓解措施
如果 MCP 客户端支持一键本地 MCP 服务器配置,则在执行命令之前 MUST 实现适当的同意机制。 配置前同意 在通过一键配置连接新的本地 MCP 服务器之前,显示一个清晰的同意对话框。MCP 客户端 MUST:- 显示将要执行的确切命令,不得截断(包括参数和参数值)
- 明确说明这是一个会在用户系统上执行代码的潜在危险操作
- 在继续之前要求用户明确批准
- 允许用户取消配置
- 高亮显示潜在危险的命令模式(例如,包含
sudo、rm -rf、网络操作、访问预期目录之外文件系统的命令) - 对访问敏感位置的命令显示警告(主目录、SSH 密钥、系统目录)
- 警告 MCP 服务器与客户端具有相同的权限运行
- 在受沙箱保护且默认权限最小的环境中执行 MCP 服务器命令
- 以受限方式启动 MCP 服务器,限制其对文件系统、网络和其他系统资源的访问
- 提供机制,使用户在需要时可以显式授予额外权限(例如,特定目录访问、网络访问)
- 使用适合平台的沙箱技术(容器、chroot、应用沙箱等)
- 及时更新沙箱方案,以应对新出现的漏洞
- 使用
stdio传输方式,将访问限制为仅限 MCP 客户端 - 如果使用 HTTP 传输,则限制访问,例如:
- 要求授权令牌
- 使用 unix 域套接字或其他具有受限访问权限的进程间通信(IPC)机制
OAuth 授权 URL 验证
恶意 MCP 服务器提供的 OAuth 授权 URL 可能利用客户端侧的 URL 处理漏洞,导致跨站脚本(XSS)攻击和远程代码执行(RCE)。攻击描述
在 OAuth 授权流程期间,MCP 服务器会提供授权 URL,客户端会在浏览器中打开这些 URL 或以编程方式处理它们。恶意服务器可以通过以下攻击向量,利用 MCP 客户端中不足的 URL 验证: JavaScript URL 注入(XSS)- 恶意 MCP 服务器将一个
javascript:URL 作为授权端点提供 - MCP 客户端将此 URL 直接传递给
window.open()或类似的浏览器 API - 浏览器执行 URL 中嵌入的 JavaScript 代码
- 攻击者在客户端应用程序中获得 JavaScript 执行上下文,可能导致会话劫持、凭据窃取或进一步利用
- 恶意 MCP 服务器提供包含 shell 命令注入载荷的 URL
- MCP 客户端使用 shell 命令(例如
cmd.exe、PowerShell 或 shell 脚本)来打开该 URL - shell 将 URL 的部分内容解释为要执行的额外命令
- 攻击者在用户系统上实现任意代码执行
stdio 传输能力结合时,
攻击者可以将基于 Web 的攻击升级为对整个系统的完全控制。请参见
代理场景中的 stdio 传输安全
了解详细的攻击向量和缓解措施。
风险
OAuth 授权 URL 漏洞会带来多种严重的安全风险:- 跨站脚本(XSS)。恶意 JavaScript 执行可能导致会话劫持、凭据窃取以及在客户端应用程序内的未授权操作。
- 远程代码执行(RCE)。通过 shell 执行进行命令注入可使攻击者以用户权限运行任意代码。
- 权限提升。XSS 与
stdio传输结合后,可将基于 Web 的攻击升级为对整个系统的完全控制。 - 数据外泄。攻击者可以访问存储在用户系统上的敏感数据、配置文件和凭据。
- 持久化。攻击者可以安装恶意软件、创建后门或修改系统配置,以实现持久访问。
缓解措施
URL Scheme 验证 MCP 客户端 必须 验证授权 URL 并拒绝危险的 scheme:- 必须 仅允许授权 URL 使用
http://和https://scheme。http://scheme 仅在本地开发期间对回环地址(如localhost、127.0.0.1或::1)可接受;生产环境中的授权 服务器 必须 使用https://。 - 必须 拒绝
javascript:、data:、file:、vbscript:及其他潜在危险的 scheme - 应该 使用基于允许列表的验证,而不是基于阻止列表的方法
- 不得 使用 shell 命令(例如
cmd.exe、sh、PowerShell)来打开 URL - 应该 使用平台特定的、非 shell 的 URL 打开机制
- 设置
script-src 'self'以防止执行内联 JavaScript - 使用
default-src 'self'以限制资源加载 - 对于需要内联脚本的动态内容,考虑使用
script-src 'nonce-<random>'
- 实施严格的 URL 解析和验证
- 拒绝包含可能被 shell 解释的特殊字符的 URL
- 考虑使用专门的 URL 净化库
- 记录可疑的授权 URL 以用于安全监控
代理场景中的 stdio 传输安全
stdio 传输本身并不具有内在脆弱性。然而,在代理架构中,如果一个单独的代理服务管理 stdio 连接,并且可以将 MCP 服务器作为子进程启动,那么它就可能成为从基于 Web 的攻击升级到完全系统失陷的关键路径。
攻击描述
重要:此攻击向量仅适用于采用代理架构的 MCP 实现,不适用于直接使用stdio 传输的情况。
在基于代理的 MCP 实现中,一个本地代理服务位于客户端和 MCP 服务器之间,并通过 stdio 传输将服务器作为子进程启动。这种架构在与客户端侧漏洞结合时,会形成一个特权升级路径:
- 攻击者通过 XSS 或其他客户端侧代码执行方式得手(例如,通过 OAuth URL 漏洞)
- 利用上述攻击向量,恶意行为者从客户端环境中获取客户端与代理之间建立的 MCP 代理认证令牌
- 恶意行为者向本地 MCP 代理服务发送经过身份验证的请求
- 代理通过
stdio传输启动任意命令(误以为它们是合法的 MCP 服务器命令) - 攻击者以用户权限实现远程代码执行
风险
- 权限提升。基于 Web 的漏洞(XSS)可通过代理命令执行升级为主机系统上的任意代码执行
- 身份验证绕过。被窃取的代理认证令牌允许未授权访问
stdio进程启动能力 - 系统失陷。攻击者可以执行 MCP 代理进程有权限运行的任何命令
缓解措施
主要防御手段是阻止能够启用此攻击向量的漏洞类别:- 实施 OAuth 授权 URL 验证 中描述的缓解措施
- 使用内容安全策略(CSP)防止来自不受信任来源的 JavaScript 执行
- 在处理之前验证并清理来自 MCP 服务器的所有输入
stdio 传输实现额外的安全控制:
- 为启动的进程实现沙箱或容器化
- 限制启动的 MCP 服务器对文件系统的访问
- 记录所有
stdio传输使用情况以便进行安全监控 - 对潜在危险命令要求额外授权
- 在可能的情况下,将代理通信隔离在单独的安全上下文中
- 对代理进程权限使用最小权限原则
- 为代理服务本身实现进程级沙箱
- 考虑在容器或受限环境中运行代理
范围最小化
糟糕的范围设计会增加令牌泄露的影响,提升用户 摩擦,并掩盖审计轨迹。攻击描述
攻击者通过(日志泄露、内存抓取或本地 拦截)获得一个携带广泛范围(files:*、db:*、
admin:*)的访问令牌,而这些范围是因为 MCP 服务器在 scopes_supported 中暴露了
所有范围,且客户端一次性全部请求而事先授予的。
该令牌使其能够进行横向数据访问、权限链式提升,并且在不重新同意整个
表面的情况下难以撤销。
风险
- 扩大的爆炸半径:被盗的宽泛令牌可启用不相关的 工具/资源访问
- 撤销摩擦更高:撤销最大权限令牌会干扰 所有工作流
- 审计噪声:单一的包罗万象范围掩盖了每次操作的用户意图
- 权限链式提升:攻击者可立即调用高风险工具, 无需进一步提权提示
- 同意流失:用户会拒绝列出过多范围的对话框
- 范围膨胀盲区:缺乏指标会使过宽请求 常态化
缓解措施
实现一种渐进式的、最小权限范围模型:- 最小初始范围集(例如
mcp:tools-basic),仅包含 低风险的发现/读取操作 - 当首次尝试特权操作时,通过有针对性的
WWW-Authenticatescope="..."挑战逐步提权 - 降级范围容忍:服务器应接受范围更小的令牌;认证服务器 MAY 发放 请求范围的子集
- 发出精确的范围挑战;避免返回完整目录
- 记录提权事件(请求的范围、授予的子集),并带上 关联 ID
- 仅从基线范围开始(或初始
WWW-Authenticate指定的范围) - 缓存最近的失败,避免对被拒绝的范围重复进入提权循环
常见错误
- 在
scopes_supported中发布所有可能的范围 - 使用通配或包罗万象的范围(
*、all、full-access) - 为了预防未来提示而捆绑无关权限
- 在每次挑战中返回整个范围目录
- 未经版本控制就静默更改范围语义
- 仅凭令牌中声明的范围就视为足够,而没有服务端 授权逻辑