FlawOpen

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

● CWE-863 · CVSS 6.8 · Средний
Исследования · FlawOpen

TypeScript SDK MCP не проверял аудиторию Bearer-токенов

GHSA-rvq5-wwqv-78pq затрагивает HTTP-серверы MCP, принимавшие токены без проверки аудитории. Обновите SDK, включите expectedResource и возвращайте проверенную аудиторию в AuthInfo.resource.[1]

💡 Простыми словами (ELI5)

Охранник принимает действительный пропуск, но не смотрит, в какое здание он открывает вход. Если один центр выдаёт пропуска в несколько зданий, может пройти пропуск для соседнего здания. Сервер MCP должен сверить назначение пропуска со своим URL ресурса.[1]

Ключевые понятия и термины

Аудитория (`aud`)
Ресурс или служба, для которых выдан токен доступа. Корректная подпись или успешная интроспекция сами по себе не доказывают, что токен предназначен этому серверу MCP.[1], [7]
`expectedResource`
Явно включаемая настройка исправленного middleware. Она сравнивает настроенный URL ресурса со значением, которое сообщает проверяющий токен, и отклоняет несовпадение с HTTP 401.[1], [2], [7]
`AuthInfo.resource`
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

В затронутой конфигурации используется HTTP Bearer-аутентификация MCP, а сервер авторизации выдаёт токены для нескольких служб. Токен для другого 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 или новее
Любой пакет с собственной проверкой aud в verifyAccessToken()В приложении уже может быть реализована проверка, отсутствующая в helper.Убедитесь, что сравнение точное и использует ожидаемый идентификатор ресурса.

Исходный код: Уязвимый vs Защищённый вариант

Пример соответствует документированному формату SDK и внесённому исправлению. verifyJwt обозначает уже используемый в приложении JWT-проверяющий код или вызов интроспекции. До исправления middleware не имел expectedResource и не сравнивал аудиторию; исправленная версия сравнивает настроенный ресурс с AuthInfo.resource. Используйте точный идентификатор, который сервер авторизации помещает в aud; пример выбирает URL аудитории с тем же origin и поручает SDK сравнить полный ресурс. Обновите SDK 1.x до 1.32.0 или новее; для 2.x используйте @modelcontextprotocol/server 2.3.0 или новее. При использовании Express обновите и @modelcontextprotocol/express до 2.0.2 или новее. Поскольку AuthInfo.resource имеет тип URL, простое значение aud, не являющееся URL, нельзя сравнить через expectedResource: строго проверяйте этот идентификатор внутри 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,
        // Ресурс не возвращается, а у уязвимого 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 →