FlawOpen

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

● CWE-863 · CVSS 6.8 · Moyenne
Recherche · FlawOpen

Le SDK TypeScript MCP ne vérifiait pas l’audience des jetons bearer

GHSA-rvq5-wwqv-78pq concerne les serveurs HTTP MCP qui acceptaient des jetons sans vérifier leur audience. Mettez à niveau, activez expectedResource et renvoyez l’audience vérifiée dans AuthInfo.resource.[1]

💡 Explication en Langage Simple (ELI5)

Un agent de sécurité accepte un badge valide sans vérifier quel bâtiment il autorise. Si le même émetteur délivre des badges pour plusieurs bâtiments, un badge destiné au bâtiment voisin peut passer. Le serveur MCP doit comparer la destination du badge à sa propre URL de ressource.[1]

Concepts Clés et Termes

Audience (`aud`)
La ressource ou le service auquel un jeton d’accès est destiné. Une signature valide ou une introspection réussie ne prouve pas à elle seule que le jeton visait ce serveur MCP.[1], [7]
`expectedResource`
Option activée explicitement dans le middleware corrigé. Elle compare l’URL de ressource configurée à celle fournie par le vérificateur du jeton et rejette toute différence avec HTTP 401.[1], [2], [7]
`AuthInfo.resource`
URL de ressource que le vérificateur déduit de données fiables de validation ou d’introspection. Le middleware corrigé en a besoin; si elle manque, les requêtes sont rejetées lorsque le contrôle est activé.[1], [2], [7]
Paquets concernés
L’avis couvre @modelcontextprotocol/sdk 1.6.0–1.31.0, @modelcontextprotocol/server 2.0.0–2.2.0, @modelcontextprotocol/express 2.0.0–2.0.1 et @modelcontextprotocol/server-legacy 2.0.0–2.3.0. Aucun CVE n’est attribué; l’éditeur classe le défaut CWE-863.[1], [3], [6]

Correspondances de référentiels

  • Référence CWE pour la faiblesse décrite dans ce guide : CWE-863

Déroulement de l'Attaque Étape par Étape

Étape 1

Un même émetteur sert plusieurs API

Le déploiement concerné utilise une authentification bearer HTTP pour MCP et son serveur d’autorisation émet des jetons pour plusieurs services. Un jeton destiné à une autre API peut être correctement signé et non expiré.[1]

Étape 2

Le jeton est valide, mais sa destination n’est pas vérifiée

Dans l’intégration concernée, requireBearerAuth() / verifyBearerToken() ne proposaient pas de contrôle intégré de la ressource attendue. Si verifyAccessToken() ne vérifiait pas non plus aud, le serveur MCP pouvait accepter un jeton destiné à un autre service.[1], [2], [5]

Étape 3

Le jeton d’un autre service atteint l’autorisation MCP

Un appelant détenant un jeton valide pour un autre service peut être authentifié auprès de ce serveur MCP. L’avis attribue CVSS 3.1 6,8 et CWE-863; il ne signale aucun incident précis d’exploitation active.[1], [4]

Étape 4

Mettez à niveau et liez la validation à cette ressource MCP

Utilisez la version corrigée, définissez expectedResource avec l’identifiant de ce serveur et faites renvoyer l’audience vérifiée par verifyAccessToken() dans AuthInfo.resource. La mise à niveau seule ne l’active pas; sans resource, toutes les requêtes reçoivent 401.[1], [2], [5], [3], [6], [7]

Paquets MCP TypeScript SDK concernés

PaquetVersions concernéesVersion corrigée
@modelcontextprotocol/sdk1.6.0 à 1.31.01.32.0 ou ultérieure
@modelcontextprotocol/server2.0.0 à 2.2.02.3.0 ou ultérieure
@modelcontextprotocol/express2.0.0 à 2.0.12.0.2 ou ultérieure
@modelcontextprotocol/server-legacy2.0.0 à 2.3.02.3.1 ou ultérieure
Tout paquet avec sa propre validation de aud dans verifyAccessToken()L’application applique peut-être déjà le contrôle absent de l’assistant.Vérifiez que la comparaison est stricte et utilise l’identifiant de ressource attendu.

Code Source : Vulnérable vs Sécurisé

Cet exemple suit la forme documentée par le SDK et le correctif intégré. verifyJwt représente le validateur JWT ou l’appel d’introspection déjà utilisé par l’application. Avant le correctif, le middleware ne proposait pas expectedResource et ne comparait pas l’audience; la version corrigée compare la ressource configurée à AuthInfo.resource. Utilisez l’identifiant exact placé dans aud par le serveur d’autorisation; l’exemple sélectionne une URL d’audience de même origine et laisse le SDK comparer la ressource complète. Mettez le SDK 1.x à niveau vers 1.32.0 ou ultérieur; pour 2.x, utilisez @modelcontextprotocol/server 2.3.0 ou ultérieur. Avec Express, mettez aussi @modelcontextprotocol/express à niveau vers 2.0.2 ou ultérieur. Comme AuthInfo.resource est une URL, une valeur aud simple qui n’est pas une URL ne peut pas être comparée avec expectedResource : validez strictement cet identifiant dans verifyAccessToken() et laissez expectedResource non configuré pour ce déploiement. L’implémentation de verifyJwt est propre à l’application et omise; aucun faux jeton ni procédure d’exploitation n’est présenté.[1], [2], [5], [3], [6], [7]

IMPLÉMENTATION VULNÉRABLE
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); // Vérification du JWT par l’application
      return {
        token,
        clientId: payload.sub,
        scopes: payload.scopes,
        expiresAt: payload.exp,
        // Aucune ressource n’est renvoyée et le helper concerné ne proposait pas l’option expectedResource.
      };
    },
  },
});
PATCH SÉCURISÉ ET ROBUSTE
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); // Vérification du JWT par l’application
      // aud peut être une chaîne, une liste ou être absent. Indiquez la ressource de ce serveur.
      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,
});

Liste de Contrôle de Sécurité pour l'Ingénierie

Sources

← AI Security Directory →