Утренний curl, который стоил ребятам работы
Представьте: вы запускаете curl -X GET https://your-llm-api.com/v1/keys и получаете обратно все API-ключи вашей платформы. Звучит как сценарий катастрофы? А это реальность, в которую попадают стартапы, копирующие конфиги из туториалов без оглядки на безопасность. В апреле 2026 года таких инцидентов становится только больше — взлом LiteLLM и утечка данных Mercor показали, что даже популярные прокси-решения могут быть дырявыми, если их неправильно развернуть.
Наша команда занимается red teaming AI-сервисов. И один из самых частых сценариев — даже не сложная инъекция промпта, а банальный HTTP-запрос к admin-ендпоинту. Без авторизации. Без проверки. Просто потому что разработчики забыли убрать отладку в продакшене. Или, что еще печальнее, считали, что «внутренняя сеть» — это надежная защита.
В этой статье — хроника реального аудита. Я покажу, как один curl может вытащить все ключи, и расскажу, как этого избежать.
Проблема: SSRF и открытые API в LLM-инфраструктуре
Большинство self-hosted LLM-платформ (LiteLLM, llama.cpp, LocalAI) используют REST API для управления ключами. По умолчанию эти эндпоинты слушают на localhost или на 0.0.0.0 без аутентификации. Команда docker-compose up -d — и сервис готов к работе. Но готов ли он к злоумышленникам?
💡 Суть уязвимости: LLM-прокси (например, LiteLLM) может иметь endpoint для создания/списка ключей. Если этот endpoint не защищен (нет заголовка Authorization или он не проверяется), любой, кто до него достучится, получит полный доступ.
Вот типичный сценарий. Вы разворачиваете LiteLLM как gateway к OpenAI, Anthropic, Mistral. Он слушает на порту 8000. Вы думаете: «У меня белый список IP, все ок». Но забываете, что внутри Kubernetes или Docker сеть может быть не изолирована. Или что ваш фронтенд также может делать запросы к внутренним сервисам (Server-Side Request Forgery). Или что кто-то забыл выключить дебаг-режим.
В результате — один запрос:
curl http://llm-gateway:8000/key/list -H "Authorization: Bearer "Если токен не проверяется или пустой — привет, список всех API-ключей.
⚠️ Важный нюанс: В некоторых версиях LiteLLM (до 1.82.8) была критическая уязвимость, позволяющая обходить проверку мастер-ключа. Если вы до сих пор не обновились — сделайте это прямо сейчас.
Как это выглядит на практике: разбор атаки
Недавно мы проводили аудит для клиента, который использовал LiteLLM + собственную обвязку. Внутренний аудит показал, что эндпоинт /key/list отвечает на запросы без токена. Вот как выглядел процесс:
- Сканируем сеть:
nmap -p 8000 10.0.0.0/24 - Находим открытый порт на контейнере lite-llm-1.
- Делаем curl:
curl -s http://10.0.0.42:8000/key/list - Получаем JSON с кучей ключей: openai, stripe, aws, и даже ключи от базы данных.
И это не теория. Аналогичная ситуация произошла с сервисом Moltbook, который взломали за 5 минут через Supabase. Там утекли 1.5M токенов и 35K email. Причина — открытый endpoint для аутентификации.
Разница только в том, что в случае LLM-платформы злоумышленник получает не просто доступ к чату, а возможность вызывать дорогие модели за ваш счет, читать промпты пользователей и подделывать ответы.
Пошаговый план аудита: проверьте свою платформу
Действуйте строго на своих тестовых окружениях. Если вы собираетесь тестировать продакшен — сначала получите письменное разрешение.
1Картографирование эндпоинтов
Соберите все endpoint'ы, которые слушает ваше LLM-приложение. Используйте netstat, lsof или просто посмотрите на docker-compose.yml. Типичные порты: 8000 (LiteLLM), 8080 (LocalAI), 8080 (llama.cpp server). Проверьте, слушает ли сервис на 0.0.0.0 (внешние запросы) или на 127.0.0.1 (только локально).
# Пример: проверить, какие порты открыты в контейнере
docker exec lite-llm-1 netstat -tulpn | grep LISTEN2Тест на анонимный доступ
Для каждого эндпоинта сделайте запрос без заголовка Authorization или с пустым/фейковым токеном. Используйте этот список:
| Эндпоинт | Типичное поведение при отсутствии авторизации |
|---|---|
| /key/list | Возвращает список всех ключей (LiteLLM) |
| /v1/models | Показывает доступные модели, иногда с ключами в конфиге |
| /admin/keys | Admin-панель управления ключами |
| /metrics | Prometheus метрики — можно увидеть эндпоинты |
Если хотя бы один из них отвечает 200 OK — у вас проблема.
3Проверка SSRF
Если ваша LLM-платформа может отправлять запросы на основе пользовательского ввода (например, функция «использовать кастомную модель»), попробуйте заставить её обратиться к внутреннему сервису:
# Пример: подмена модели на http://internal-server/key/list
curl -X POST https://your-llm.com/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model": "http://localhost:8000/key/list", "messages": [{"role": "user", "content": "test"}]}'Если в ответ придет не ошибка, а содержимое внутреннего эндпоинта — SSRF работает. Полное руководство по AI Red Teaming описывает аналогичную технику атаки на Grok от xAI.
4Анализ логов и конфигов
Посмотрите на файлы конфигурации. В них часто хардкодят ключи. Используйте grep -r "sk-" . в корневой директории проекта. Или сканируйте гитисторию. Как показал случай с атакой на цепочку поставок LiteLLM в PyPI, ключи могут быть вкомпилены в образ.
Нюансы и типичные ошибки
Теперь о том, что чаще всего идёт не так:
- Сетевая изоляция — не панацея. Если ваш LLM-сервис стоит рядом с фронтендом, а фронтенд компрометируется (например, XSS), злоумышленник может сделать запрос от имени браузера и получить доступ к внутренним API. Решение — использовать отдельный API-ключ для каждого сервиса и никогда не доверять внутренней сети.
- Debag-режим в продакшене. Многие разработчики забывают выключить
LITELLM_DEV_MODE=true. В этом режиме авторизация отключена. Проверьте переменные окружения. - Отсутствие rate limiting. Даже если ключ защищен, атакующий может перебирать токены. Без лимита это вопрос времени. Практические методы защиты от prompt injection включают и rate-limiting.
- Хранение ключей в ENV без секрет-менеджера. ENV-переменные доступны внутри контейнера каждому процессу. Если злоумышленник получит RCE (а критическая дыра в llama.cpp такое позволяет), он прочитает все ключи. Используйте Vault или AWS Secrets Manager.
💡 Лайфхак: Добавьте в CI/CD шаг, который прогоняет curl против каждого развернутого сервиса с пустым токеном. Если вернулось не 401 — сломайте билд.
Что делать, если вы нашли уязвимость
Ни в коем случае не паникуйте и не исправляйте на горячую — так можно положить весь сервис. Следуйте плану:
- Зафиксируйте доказательства. Сделайте скриншот или запишите curl-команду и ответ.
- Отключите внешний доступ. Если эндпоинт был открыт на всю сеть, временно заблокируйте его через сетевой ACL (iptables/security group).
- Смените все ключи. Немедленно выполните ротацию. Пример с инструкцией по ротации credentials — отличная база.
- Проверьте логи. Ищите подозрительные запросы к этому эндпоинту за последние дни. Возможно, утечка уже произошла.
- Сообщите команде и руководству. Без обвинений, с фокусом на исправлении.
И главное — не считайте, что «это не про нас». Когда ваш ИИ становится утечкой, исправлять всегда дороже, чем предупреждать.
Чек-лист для надежной защиты
- ✔ Все административные эндпоинты закрыты за API-ключом с минимальными правами.
- ✔ Сервис не слушает на 0.0.0.0 без необходимости.
- ✔ Используется секрет-менеджер, а не ENV-переменные для ключей.
- ✔ Включен rate-limiting и audit-логирование.
- ✔ Регулярные сканирования на уязвимости (внедрите в CI/CD).
- ✔ Команда обучена безопасным практикам развертывания.
Помните: самый опасный curl — тот, который вы не проверили. Завтра этот запрос может прийти не от вас, а от хакера. Не дайте ему шанса.