介绍
目的和范围
本文档为模型上下文协议(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
- 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 或会话 不得
在用户于 MCP 服务器授权端点批准同意页面之前
设置。在同意批准之前设置此 cookie 会使同意页面失效,
因为攻击者可以通过构造恶意授权请求绕过它。
Token 透传
“Token 透传”是一种反模式,即 MCP 服务器在未验证这些 token 是否已正确签发给 该 MCP 服务器 的情况下,接受来自 MCP 客户端的 token,并将其传递给下游 API。风险
在 授权规范 中,Token 透传被明确禁止,因为它会引入多种安全风险,包括:- 安全控制绕过
- MCP 服务器或下游 API 可能会实现重要的安全控制,例如速率限制、请求校验或流量监控,而这些控制依赖于 token 的受众(audience)或其他凭证约束。如果客户端能够直接使用 token 与下游 API 通信,而 MCP 服务器未正确验证这些 token 或未确保它们是为正确的服务签发的,那么这些控制就会被绕过。
- 问责与审计追踪问题
- 当客户端使用的是上游签发的访问 token,而该 token 对 MCP 服务器来说可能是不可读的,MCP 服务器将无法识别或区分不同的 MCP 客户端。
- 下游资源服务器的日志中可能会显示请求似乎来自不同的来源、具有不同的身份,而不是实际转发这些 token 的 MCP 服务器。
- 这两个因素都会使事件调查、控制和审计更加困难。
- 如果 MCP 服务器在未验证其声明(例如角色、权限或受众)或其他元数据的情况下传递 token,那么持有被盗 token 的恶意行为者就可以将该服务器用作数据外泄的代理。
- 信任边界问题
- 下游资源服务器会对特定实体授予信任。这种信任可能包括对来源或客户端行为模式的假设。破坏这一信任边界可能导致意外问题。
- 如果一个 token 在未经适当验证的情况下被多个服务接受,那么攻击者在攻破某个服务后,就可以使用该 token 访问其他已连接的服务。
- 未来兼容性风险
- 即使某个 MCP 服务器今天是“纯代理”,它将来仍可能需要添加安全控制。从一开始就正确地分离 token 受众,会让安全模型更容易演进。
缓解措施
MCP 服务器 MUST NOT 接受任何未明确签发给该 MCP 服务器的 token。服务端请求伪造(SSRF)
服务端请求伪造(SSRF)是一种攻击,攻击者可以诱导 MCP 客户端向非预期的目的地发起 HTTP 请求,从而可能访问内部网络资源、云元数据端点或其他受保护的服务。攻击描述
在 OAuth 元数据发现过程中,MCP 客户端会从多个可能被恶意 MCP 服务器控制的来源获取 URL:WWW-Authenticate头中的resource_metadataURL- Protected Resource Metadata 文档中的
authorization_serversURLs - Authorization Server Metadata 中的
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 endpoint)可能会触发对内部服务的变更
- 防火墙绕过: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 发送恶意事件。
- 当服务器支持 重传/可恢复流时, 故意在接收响应之前终止请求,可能会导致原始客户端通过用于服务器发送事件的 GET 请求恢复该请求。
- 如果某个服务器会因诸如
notifications/tools/list_changed之类的工具调用而启动服务器发送事件,并且能够影响 服务器提供的工具,那么客户端最终可能会获得其并不知道已启用的工具。
- Server B 将该事件(与会话 ID 关联)入队到共享队列中。
- Server A 使用会话 ID 轮询队列获取事件,并检索到恶意载荷。
- Server 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,也无法冒充其他用户,因为 user ID 是从用户令牌派生的,而不是由客户端提供的。
MCP 服务器可以选择性地利用额外的唯一标识符。
本地 MCP 服务器入侵
本地 MCP 服务器是运行在用户本地机器上的 MCP 服务器, 可以是由用户下载并执行的服务器、用户自己编写的服务器, 或者通过客户端的配置流程安装的服务器。 这些服务器可能直接访问用户的系统,也可能被用户机器上运行的其他进程访问,因此成为攻击的诱人目标。攻击描述
本地 MCP 服务器是与 MCP 客户端运行在同一台机器上并被下载和执行的二进制文件。如果没有适当的沙箱隔离和同意要求,以下攻击就会成为可能:- 攻击者在客户端配置中包含恶意的“启动”命令
- 攻击者在服务器本身中分发恶意载荷
- 攻击者通过 DNS 重绑定访问留在 localhost 上运行的不安全本地服务器
风险
来自不受信任来源或限制不足的本地 MCP 服务器会带来若干严重的安全风险:- 任意代码执行。攻击者可以使用 MCP 客户端权限执行任何命令。
- 缺乏可见性。用户无法了解正在执行哪些命令。
- 命令混淆。恶意行为者可以使用复杂或晦涩的命令来伪装成合法行为。
- 数据窃取。攻击者可以通过被入侵的 JavaScript 访问合法的本地 MCP 服务器。
- 数据丢失。攻击者或合法服务器中的漏洞都可能导致主机上的数据不可恢复地丢失。
缓解措施
如果 MCP 客户端支持一键式本地 MCP 服务器配置,则在执行命令之前,必须实现适当的同意机制。 配置前同意 在通过一键配置连接新的本地 MCP 服务器之前,显示一个清晰的同意对话框。MCP 客户端 必须:- 显示将要执行的确切命令,不得截断(包括参数和选项)
- 清楚地将其标识为会在用户系统上执行代码的潜在危险操作
- 在继续之前要求用户明确批准
- 允许用户取消配置
- 高亮显示潜在危险的命令模式(例如,包含
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,或以编程方式处理这些 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) - 捆绑无关权限,以避免未来再次提示
- 在每次质询中返回完整的范围目录
- 未进行版本控制就静默更改范围语义
- 将令牌中声明的范围视为充分条件,而不执行服务器端授权逻辑