引言
目的和范围
本文档为 Model Context Protocol(MCP)提供安全性考虑事项,并补充 MCP 授权 规范。本文档识别特定于 MCP 实现的安全风险、攻击向量 以及最佳实践。 本文档的主要受众包括实现 MCP 授权流程的开发人员、MCP 服务器运营者,以及评估基于 MCP 的系统的安全 专业人员。本文档应与 MCP 授权规范以及 OAuth 2.0 安全最佳实践一起阅读。攻击与缓解措施
本节详细描述了针对 MCP 实现的攻击,以及可能的应对措施。困惑代理问题
攻击者可以利用连接到第三方 API 的 MCP 代理服务器,创建 “困惑代理” 漏洞。该攻击允许恶意客户端通过利用静态客户端 ID、动态客户端注册和同意 cookie 的组合,在没有适当用户同意的情况下获取授权码。术语
MCP 代理服务器 : 一个将 MCP 客户端连接到第三方 API 的 MCP 服务器,提供 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 代理服务器 必须 按如下所述实现 按客户端同意和适当的安全控制。同意流程实现
下图展示了如何正确实现应在第三方授权流程 之前运行的按客户端同意:所需防护
按客户端同意存储 MCP 代理服务器 必须:- 为每个用户维护已批准
client_id值的注册表 - 在启动第三方 授权流程之前检查此注册表
- 安全地存储同意决定(服务端数据库,或特定于服务器的 cookie)
- 清晰标识请求的 MCP 客户端名称
- 显示所请求的具体第三方 API 作用域
- 显示令牌将被发送到的已注册
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 或会话。若在同意批准之前设置该 cookie,
则会使同意页面失效,因为攻击者可以通过构造恶意授权请求来绕过它。
Token 透传
“Token 透传” 是一种反模式,指的是 MCP 服务器在没有验证这些 token 是否已正确发放给 MCP 服务器 的情况下,从 MCP 客户端接收 token,并将其转发给下游 API。风险
在 authorization specification 中,token 透传被明确禁止,因为它会引入多种安全风险,包括:- 安全控制绕过
- MCP 服务器或下游 API 可能实现了重要的安全控制,例如速率限制、请求验证或流量监控,这些控制依赖于 token 的受众或其他凭证约束。如果客户端能够在不经由 MCP 服务器正确验证,或不确保 token 是为正确服务签发的情况下,直接与下游 API 使用这些 token,它们就会绕过这些控制。
- 问责与审计追踪问题
- 当客户端使用上游签发的访问 token 调用时,MCP 服务器将无法识别或区分不同的 MCP 客户端,而该 token 对 MCP 服务器来说可能是不可见的。
- 下游资源服务器的日志可能会显示这些请求似乎来自不同的来源、具有不同的身份,而不是实际上在转发这些 token 的 MCP 服务器。
- 这两个因素都会使事件调查、控制和审计更加困难。
- 如果 MCP 服务器在不验证其声明(例如角色、权限或受众)或其他元数据的情况下传递 token,那么持有被盗 token 的恶意行为者就可以把该服务器当作数据外传的代理。
- 信任边界问题
- 下游资源服务器只向特定实体授予信任。这种信任可能包含对来源或客户端行为模式的假设。破坏这一信任边界可能会导致意想不到的问题。
- 如果一个 token 在未经适当验证的情况下被多个服务接受,那么攻破其中一个服务的攻击者就可以使用该 token 访问其他已连接的服务。
- 未来兼容性风险
- 即使某个 MCP 服务器今天作为“纯代理”启动,未来也可能需要添加安全控制。从一开始就正确地进行 token 受众隔离,会使安全模型更容易演进。
缓解措施
MCP 服务器 不得 接受任何未明确发放给 MCP 服务器的 token。服务器端请求伪造(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 服务器时,以下攻击向量是可能的: 会话劫持提示注入- 客户端连接到 服务器 A 并接收一个会话 ID。
-
攻击者获取一个现有的会话 ID,并使用该会话 ID 向 服务器 B 发送恶意事件。
- 当服务器支持 重发/可恢复流 时, 在接收响应之前故意终止请求,可能会导致原始客户端通过用于服务器发送事件的 GET 请求恢复该请求。
- 如果某个服务器会因工具调用而启动服务器发送事件,例如
notifications/tools/list_changed,并且这会影响服务器提供的工具,则客户端最终可能会获得其并不知道已启用的工具。
- 服务器 B 将该事件(与会话 ID 关联)加入共享队列。
- 服务器 A 使用会话 ID 轮询队列中的事件,并检索到恶意载荷。
- 服务器 A 将恶意载荷作为异步或恢复的响应发送给客户端。
- 客户端接收并根据恶意载荷采取行动,导致潜在的入侵。
- MCP 客户端与 MCP 服务器进行认证,创建一个持久会话 ID。
- 攻击者获取该会话 ID。
- 攻击者使用该会话 ID 调用 MCP 服务器。
- MCP 服务器不检查额外授权,并将攻击者视为合法用户,从而允许未授权访问或操作。
缓解措施
为防止会话劫持和事件注入攻击,应实施以下缓解措施: 实现授权的 MCP 服务器 MUST 验证所有入站请求。MCP 服务器 MUST NOT 将会话用于认证。 MCP 服务器 MUST 使用安全、不可预测的会话 ID。 生成的会话 ID(例如 UUID)SHOULD 使用安全随机数生成器。避免使用可预测或顺序递增的会话标识符,因为攻击者可能猜测出来。轮换或过期会话 ID 也可以降低风险。 MCP 服务器 SHOULD 将会话 ID 与特定用户信息绑定。 在存储或传输与会话相关的数据(例如在队列中)时,将会话 ID 与授权用户独有的信息组合起来,例如其内部用户 ID。使用类似<user_id>:<session_id> 的键格式。这样即使攻击者猜到会话 ID,也无法冒充其他用户,因为用户 ID 是从用户令牌派生而来,而不是由客户端提供的。
MCP 服务器可以选择利用额外的唯一标识符。
本地 MCP 服务器被攻破
本地 MCP 服务器是运行在用户本地机器上的 MCP 服务器, 可以是用户下载并执行的服务器、用户自行编写的服务器,或通过客户端的配置流程安装的服务器。 这些服务器可能直接访问用户系统,也可能被用户机器上的其他进程访问,因此很容易成为攻击目标。攻击描述
本地 MCP 服务器是下载后在与 MCP 客户端相同的机器上执行的二进制程序。 如果没有适当的沙箱隔离和同意要求,以下攻击就成为可能:- 攻击者在客户端配置中包含恶意的“启动”命令
- 攻击者在服务器本身中分发恶意载荷
- 攻击者通过 DNS 重新绑定访问一个未加固、仍在 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="..."挑战逐步提升权限 - 降级范围容忍:服务器应接受已缩减范围的令牌;认证服务器 可以签发所请求范围的子集
- 发出精确的范围挑战;避免返回完整目录
- 记录提升事件(请求的范围、授予的子集),并带上 关联 ID
- 仅以基线范围(或初始
WWW-Authenticate指定的范围)开始 - 缓存最近的失败,避免对被拒绝的范围反复进入提升循环
常见错误
- 在
scopes_supported中发布所有可能的范围 - 使用通配符或包罗万象的范围(
*、all、full-access) - 将无关权限捆绑在一起以预先避免未来提示
- 在每次挑战中返回整个范围目录
- 在没有版本管理的情况下静默更改范围语义
- 在没有服务器端 授权逻辑的情况下,认为令牌中声明的范围就足够了