flawopen.com/AI Security/mcp-python-sdk-oauth-issuer-confusion
Ошибка проверки issuer OAuth в MCP Python SDK
GHSA-qx49-fqc8-xw99 позволяла HTTP-серверу MCP влиять на адрес, куда затронутые OAuth-клиенты Python SDK могли отправить учётные данные. Обновитесь до mcp 1.30.0 или 2.2.0; для машинных провайдеров необходимо задать issuer=. CVE не присвоен.[1], [3], [8]
Компания отправляет запечатанный ключ в банк, адрес которого указан в утверждённой карточке справочника. Сервер MCP мог предоставить эту карточку, а резервный путь не проверял, что запись относится к ожидаемому банку. Проверка issuer сопоставляет идентификатор в карточке с доверенным банком из конфигурации, прежде чем отправить ключ.[1], [2]
Ключевые понятия и термины
Клиент MCP- Приложение, использующее Model Context Protocol для доступа к инструментам или данным сервера MCP. Уязвимость затрагивает Python SDK в роли HTTP-клиента с OAuth-провайдерами.[1]
Issuer сервера авторизации- Стабильный идентификатор OAuth-службы, выдающей токены. Клиент должен сравнить обнаруженные метаданные с issuer, ожидаемым независимо от них.[1], [2]
Protected Resource Metadata (PRM)- Метаданные, которые могут указывать серверы авторизации защищённого ресурса. При отсутствии PRM SDK также использует устаревший путь обнаружения.[1], [2]
Связи со стандартами
Пошаговый сценарий атаки
HTTP-клиент MCP хранит OAuth-учётные данные
Приложение использует затронутую версию mcp и один из OAuth-провайдеров SDK. Оно может подключаться к не полностью доверенному серверу MCP, имея секрет клиента, ключ подписи или сохранённую регистрацию клиента.[1]
Сервер влияет на обнаружение авторизации
В mcp 1.9.1–1.29.1 не проверялся issuer и не выполнялась привязка учётных данных на всех путях обнаружения. mcp 2.0.0–2.1.1 был уязвим при отсутствии PRM и использовании legacy fallback либо при обнаружении после 403 insufficient_scope. В обеих ветках у машинных провайдеров не было issuer= для привязки заранее выданных учётных данных. Диапазон пакета в advisory начинается с предварительной версии 2.0.0a1 и ограничен <2.2.0; следовательно, затронутые предварительные сборки 2.0.0 также входят в него.[1], [3], [8]
Последствия зависят от провайдера и прав
Получатель может использовать раскрытые учётные данные или токены в пределах выданных разрешений. В уведомлении для автономных провайдеров указан CVSS 7,5; для интерактивного — 6,5, поскольку вход должен начать человек. В уведомлении сказано, что CVE не присвоен.[1]
Матрица версий и действий для OAuth в MCP Python SDK
| Конфигурация клиента | Документированное условие уязвимости | Необходимое действие |
|---|---|---|
HTTP OAuth-клиент с mcp 1.9.1–1.29.1 | Во всех путях обнаружения не хватало проверки issuer и последовательной привязки учётных данных к нему. | Обновитесь до mcp 1.30.0 или новее и проверьте фактически развёрнутую версию. |
HTTP OAuth-клиент с mcp 2.0.0–2.1.1 | Уязвим при отсутствии PRM и срабатывании старого fallback либо при повторном обнаружении после 403 insufficient_scope. Диапазон пакета в advisory начинается с предварительной версии 2.0.0a1 и ограничен <2.2.0; следовательно, затронутые предварительные сборки 2.0.0 также входят в него. | Обновитесь до mcp 2.2.0 или новее и проверьте фактически развёрнутую версию. |
ClientCredentialsOAuthProvider или PrivateKeyJWTOAuthProvider с заранее выданными учётными данными | В уязвимых версиях отсутствовал параметр issuer= для привязки этих данных к ожидаемому серверу авторизации. | После обновления явно задайте issuer=. Перейдите с устаревшего RFC7523OAuthClientProvider в ветке 1.x. |
| Созданный SDK сервер, stdio-клиент или клиент со своим токеном/заголовком | В advisory указано, что они не затронуты. | Для этой конкретной ошибки действий не требуется; проверьте приложение повторно, если оно также использует затронутый HTTP OAuth-клиент. |
Анализ первопричины
У этой ошибки доверия было две связанные причины: некоторые затронутые пути обнаружения не всегда сравнивали issuer в метаданных сервера авторизации со значением, ожидаемым независимо от MCP-сервера, а сохранённые или заранее заданные учётные данные не были привязаны к своему серверу авторизации. Поэтому вредоносный или скомпрометированный MCP-сервер мог направить обмен токенов на контролируемую им конечную точку и получить OAuth-данные клиента. Исправления определяют ожидаемый issuer до обнаружения, отклоняют несовпадения и привязывают сохранённые регистрации к этому issuer; для автоматических провайдеров machine-to-machine также требуется явно указать issuer=.[1], [2]
Исходный код: Уязвимый vs Защищённый вариант
Аргумент issuer доступен в исправленных версиях SDK. Он связывает машинные учётные данные с нужным сервером авторизации; это не заменяет обновление, удаление старых непривязанных регистраций и ротацию учётных данных, которые могли раскрыться.[2], [3], [8]
Фрагмент исходного кода: mcp.client.auth
import os
from mcp.client.auth import ClientCredentialsOAuthProvider
auth = ClientCredentialsOAuthProvider(
client_id=os.environ["MCP_CLIENT_ID"],
client_secret=os.environ["MCP_CLIENT_SECRET"],
)
import os
from mcp.client.auth import ClientCredentialsOAuthProvider
auth = ClientCredentialsOAuthProvider(
client_id=os.environ["MCP_CLIENT_ID"],
client_secret=os.environ["MCP_CLIENT_SECRET"],
issuer=os.environ["MCP_AUTH_ISSUER"],
)
# Используйте mcp >= 1.30.0 (1.x) или >= 2.2.0 (2.x).
Чек-лист по защите системы для инженеров
- ✓Проверьте HTTP-клиенты MCP и установленную версию пакета
mcp, включая транзитивные зависимости и образы контейнеров.[1] - ✓Обновите клиентов 1.x до
mcp1.30.0 или новее, а клиентов 2.x — до 2.2.0 или новее; проверьте lock-файл и развёрнутую среду.[1], [3], [8] - ✓Явно укажите
issuer=дляClientCredentialsOAuthProviderиPrivateKeyJWTOAuthProvider; перейдите с устаревшегоRFC7523OAuthClientProvider1.x, у которого такой опции нет.[1], [2] - ✓Удалите сохранённые OAuth-регистрации, созданные до привязки issuer, и зарегистрируйте их заново. Для предварительно созданной записи задайте issuer явно.[1], [2]
- ✓Рекомендация FlawOpen по реализации: Считайте недоверенные серверы MCP границей, которой могут быть переданы учётные данные. Не подключайте их к клиентам с производственными OAuth-секретами или ключами без явного разрешения политики.[1]
- ✓Если затронутый клиент мог подключаться к недоверенному серверу, смените секрет клиента и отзовите токены на сервере авторизации.[1]
- ✓Добавьте регрессионные тесты для путей обнаружения 1.x и 2.x, несовпадающих issuer, отсутствия PRM,
403 insufficient_scope, привязанных к issuer регистраций и машинных провайдеров безissuer=.[2] - ✓Проверьте область действия advisory, прежде чем считать затронутыми другие клиенты: созданные SDK серверы, stdio-клиенты и клиенты со своими токенами или заголовками не затронуты.[1]
Источники
- GitHub-reviewed advisory GHSA-qx49-fqc8-xw99
- MCP Python SDK pull request #3398: Validate issuer on every discovery path
- MCP Python SDK v1.30.0 release notes
- MITRE CWE-345: Insufficient Verification of Data Authenticity
- The Hacker News: Official MCP Python SDK flaw can let malicious servers steal OAuth credentials
- Cycode disclosure: MCP Python SDK OAuth flaw
- CWE-522: Insufficiently Protected Credentials
- MCP Python SDK v2.2.0 release