FlawOpen

flawopen.com/AI Security/mcp-typescript-sdk-bearer-token-audience-validation

● CWE-863 · CVSS 6.8 · 中危
安全研究 · FlawOpen

MCP TypeScript SDK 未验证 Bearer 令牌的受众

GHSA-rvq5-wwqv-78pq 影响未检查令牌受众便接受令牌的 MCP HTTP 服务器。升级后启用 expectedResource,并将已验证的受众返回为 AuthInfo.resource。[1]

💡 通俗易懂的原理解析 (ELI5)

门卫接受了有效门卡,却没有核对它能打开哪栋楼。若同一机构为多栋楼发卡,隔壁楼的门卡也可能通过。MCP服务器必须把门卡的目标与自己的资源URL逐项比对,再决定是否放行。[1]

核心概念与专有名词

受众(`aud`)
访问令牌获准访问的资源或服务。签名有效或 introspection 成功,并不能单独证明令牌是发给当前 MCP 服务器的。[1], [7]
`expectedResource`
修复版中需要显式启用的设置。它将配置的资源 URL 与令牌验证器报告的资源比较;不匹配时返回 HTTP 401。[1], [2], [7]
`AuthInfo.resource`
验证器根据可信的令牌验证或 introspection 数据推导出的资源 URL。启用检查后,修复版 middleware 需要此值;缺失时请求会被拒绝。[1], [2], [7]
受影响的软件包
公告涵盖 @modelcontextprotocol/sdk 1.6.0–1.31.0、@modelcontextprotocol/server 2.0.0–2.2.0、@modelcontextprotocol/express 2.0.0–2.0.1,以及 @modelcontextprotocol/server-legacy 2.0.0–2.3.0。未分配 CVE;供应方归类为 CWE-863。[1], [3], [6]

框架映射

  • 本指南所述弱点的 CWE 参考: CWE-863

攻击执行流程分解

步骤 1

同一授权方为多个 API 发放令牌

受影响部署使用 MCP HTTP Bearer 身份验证,授权服务器会为多个服务签发访问令牌。发给其他 API 的令牌仍可能签名正确且未过期。[1]

步骤 2

验证了令牌,却没有验证目标

受影响集成中的 requireBearerAuth() / verifyBearerToken() 没有内置预期资源检查。如果应用的 verifyAccessToken() 也未检查 aud,MCP 服务器可能接受发给其他服务的令牌。[1], [2], [5]

步骤 3

其他服务的令牌进入 MCP 授权层

持有其他服务有效令牌的调用方可能被当前 MCP 服务器视为已认证。公告给出的评分为 CVSS 3.1 6.8,并关联 CWE-863;没有描述已知在野利用事件。[1], [4]

步骤 4

升级并将验证绑定到 MCP 资源

使用修复版本,将 expectedResource 设为本服务器的资源标识,并让 verifyAccessToken() 将已验证受众作为 AuthInfo.resource 返回。仅升级不会启用该选项;缺少 resource 时所有请求都会收到 401。[1], [2], [5], [3], [6], [7]

受影响的 MCP TypeScript SDK 软件包

软件包受影响版本修复版本
@modelcontextprotocol/sdk1.6.0–1.31.01.32.0 或更高
@modelcontextprotocol/server2.0.0–2.2.02.3.0 或更高
@modelcontextprotocol/express2.0.0–2.0.12.0.2 或更高
@modelcontextprotocol/server-legacy2.0.0–2.3.02.3.1 或更高
在 verifyAccessToken() 中自行校验 aud 的任何软件包应用可能已经实现了 helper 中缺失的校验。确认比较是严格匹配,并使用预期的资源标识符。

源代码对比:漏洞与安全实现

此示例遵循 SDK 文档格式及已合并的修复。verifyJwt 表示应用已有的 JWT 验证器或 introspection 调用。修复前,middleware 没有 expectedResource,也不会比较受众;修复版会比较配置的资源与 AuthInfo.resource。应使用授权服务器写入 aud 的准确标识;示例选择同一 origin 下的受众 URL,再由 SDK 比较完整资源。将 1.x SDK 升级到 1.32.0 或更高版本;2.x 使用 @modelcontextprotocol/server 2.3.0 或更高版本。使用 Express 时,还需将 @modelcontextprotocol/express 升级到 2.0.2 或更高版本。由于 AuthInfo.resource 的类型是 URL,无法通过 expectedResource 比较不属于 URL 的普通 aud 值:应在 verifyAccessToken() 内严格验证该标识,并在这种部署中不要设置 expectedResource。 verifyJwt 的实现由应用自行提供,此处省略;示例不涉及伪造令牌或利用步骤。[1], [2], [5], [3], [6], [7]

存在漏洞的实现
import { requireBearerAuth } from '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js';

const mcpServerUrl = new URL('https://api.example.com/mcp');
const auth = requireBearerAuth({
  verifier: {
    async verifyAccessToken(token) {
      const payload = await verifyJwt(token); // 应用执行 JWT 验证
      return {
        token,
        clientId: payload.sub,
        scopes: payload.scopes,
        expiresAt: payload.exp,
        // 此处未返回 resource,受影响的 helper 也没有 expectedResource 选项。
      };
    },
  },
});
加固后的安全修复
import { requireBearerAuth } from '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js';

const mcpServerUrl = new URL('https://api.example.com/mcp');
const auth = requireBearerAuth({
  verifier: {
    async verifyAccessToken(token) {
      const payload = await verifyJwt(token); // 应用执行 JWT 验证
      // aud 可能是字符串、列表,也可能不存在。返回此服务器对应的条目。
      const audience = [payload.aud ?? []].flat().find(
        aud => URL.canParse(aud) && new URL(aud).origin === mcpServerUrl.origin,
      );
      const resource = audience ? new URL(audience) : undefined;
      return {
        token,
        clientId: payload.sub,
        scopes: payload.scopes,
        expiresAt: payload.exp,
        resource,
      };
    },
  },
  expectedResource: mcpServerUrl,
});

工程与系统安全加固清单

参考来源

← AI Security Directory →