Руководства по безопасности

Из кода, написанного ИИ, утёк API-ключ, и им воспользовались для мошеннических списаний: причиной была незакрытая уязвимость CVSS 10.0

У приложения, собранного с ИИ, украли API-ключ, и начались мошеннические списания. Причиной была незакрытая RCE с CVSS 10.0. Как её нашли и как не допустить.

Опубликовано 2026-06-07 Обновлено 2026-10-06 13 мин чтения
На этой странице

Случай, с которым часто сталкиваются инди-разработчики без опыта в безопасности, с убранными всеми идентифицирующими деталями и превращённый в урок. Шагов для воспроизведения атаки здесь нет. Цель — защита, чтобы та же ошибка не случилась с вами.

Карточка инцидента — INCIDENT FILE
Класс
Утечка API-ключа / мошеннические списания
Серьёзность
Критическая (злоупотребление опубликованной CVSS 10.0)
Корневая причина
Pre-auth RCE в веб-фреймворке, месяцами оставшаяся незакрытой
Масштаб утечки
Все секреты окружения
Масштаб выполнения
Осталось внутри непривилегированного контейнера (root хоста не пострадал)
Постоянное решение
①обновиться до исправленной версии ②сменить все ключи ③машинный мониторинг CVE
10.0
CVSS / худший класс
месяцы
как долго не закрывали
всё окружение
предполагаемый масштаб утечки
мониторинг
самая эффективная профилактика

«Остановили списания» ≠ «разобрались»

Остановить счёт — временная мера. Закрыть путь утечки — отдельная задача. Вы закончили только тогда, когда сделали и то, и другое.

Что произошло (хронология)

  1. День 0 — выкатили и запустили

    Приложение, собранное с помощью ИИ, было выпущено и работало в продакшене.
  2. Однажды — счёт взлетает

    Счёт за облачный ИИ внезапно подскакивает. Массовые задания на модели, которой никогда бы не пользовались, делающие чужую работу.
  3. Расследование — злоупотребление со стороны

    «Наше приложение сошло с ума» этого не объясняет. Кто-то посторонний запускал его с украденным ключом.
  4. Поиск — выясняем путь утечки

    «Так откуда утёк ключ?» С этого вопроса и началось настоящее расследование.

Почему сразу не поймали

В том порядке, в котором совершались ошибки, — потому что именно ошибки и есть урок.

1

Слишком рано определились с причиной

Первое предположение: «наш собственный резервный код дал сбой». Но данные (что на самом деле обрабатывалось) ясно показали, что дело не в этом.
→ Прежде чем решить «моя вина» или «их вина», сначала посмотрите на данные.
2

Чистый grep чуть не завершил поиск

Весь код, история git, связанные репозитории прочёсаны → нигде нет открытых ключей. Чуть не сдались с выводом «отследить невозможно». Глядя только на файлы, упустили утечку в среде выполнения.
3

Скрытие симптома показалось исправлением

Маскировка симптома на прокси чуть не сошла за «готово». Но маскировка оставляет RCE на месте. Её можно эксплуатировать, пока не устранена причина.

Чистый grep — это ещё не повод успокоиться

Даже если ни в одном файле нет ключа, уязвимость может вытащить переменные окружения из работающего процесса. Утечки случаются в среде выполнения (RCE, HTTP-заголовки), а не только в файлах. См. Что такое RCE.

Настоящая причина: опубликованная CVSS 10.0, оставленная без исправления

Странная сигнатура в старых логах доступа быстро совпала с данными threat intelligence. У используемого диапазона версий веб-фреймворка была опубликованная pre-auth RCE (CVSS 10.0), которую уже эксплуатировали в реальных атаках, — и она месяцами оставалась незакрытой.

  • Это была не пассивная ошибка — злоумышленник выполнял код на сервере и выкачивал окружение.
  • Ущерб ограничило одно: выполнение осталось внутри непривилегированного контейнера (root хоста не захватили). Разбор взлома не нашёл устойчивого бэкдора, майнера или C2 — подтверждённым ущербом была кража секретов.
  • Поскольку внутренняя БД была достижима, в ответных мерах исходили из того, что содержимое БД тоже утекло.
Видимый симптом: внезапный неузнаваемый счёт
владелец видит только это
↑ украденные ключи позже перепродают / используют
выкачано всё окружение (каждый секрет)
↑ злоумышленник выполняет произвольный код на живом сервере
причина: опубликованная CVSS 10.0, оставленная без исправления (pre-auth RCE)
Внезапный счёт — лишь видимый симптом. Под ним причина: опубликованная CVSS 10.0, оставленная без исправления.

Урок: когда что-то ведёт себя странно, первым делом подозревайте известную CVE. Не считайте по умолчанию, что это ваш собственный баг.

Сузьте путь утечки: где хранился этот ключ?

Если grep ничего не находит, измените способ поиска. Перечислите, где был записан ключ, которым злоупотребили, — и круг возможных путей утечки сузится. Если, например, этот ключ существовал только в переменных окружения рабочего сервера, подозревайте рабочий сервер или хостинг-провайдера, а не Git или браузер.

Где утекаетЧастая причинаКак проверить свою настройкуКак закрыть
История Git / публичные репозиторииКоммит .env, ключи прямо в кодеПросканируйте всю историю через gitleaks git. Проверьте оповещения secret scanning на GitHubСначала отзовите, затем блокируйте до коммита
JavaScript, отправляемый в браузерКлюч в переменной NEXT_PUBLIC_, вызов API прямо из браузераНайдите префикс ключа в результатах сборкиПеренесите вызов API в серверный код
Публичная веб-папка.env или резервные копии в публичной папкеОткройте /.env на своём сайте и убедитесь, что возвращается 404 или 403Вынесите секретные файлы за пределы публичной папки
Логи / страницы ошибокВыпуск с включённым отладочным выводом, запись заголовков запросов как естьПроверьте отладочные настройки продакшена и не появляются ли Authorization и подобные в логахОтключите отладочный вывод и маскируйте секреты в логах
Работающий серверОпубликованная уязвимость (например, RCE), оставленная без исправленияСравните реально работающую версию с исправленной версией в бюллетенеОбновитесь, затем замените каждый секрет окружения
Хостинг-провайдерВзлом на стороне провайдераПроверьте бюллетени безопасности и уведомления провайдера по почтеХраните секреты так, чтобы значения нельзя было просмотреть позже, и меняйте их
Ваша машина разработкиВредоносный пакет или расширениеПроверьте, совпадают ли установленные версии пакетов или расширений с опубликованной компрометациейУдалите эту версию и замените секреты, которые были на машине

Этот случай — строка «Работающий сервер». Четыре нижние строки — пути, которые не оставляют следов в вашем коде или Git. Шпаргалка OWASP по управлению секретами (Secrets Management Cheat Sheet) также предупреждает, что переменные окружения часто доступны другим процессам в той же среде и могут попасть в логи или дампы. Поэтому, когда злоупотребили одним ключом, считайте утёкшими все секреты, хранившиеся в том же месте.

Отдельные ключи ускоряют расследование

Если для каждого сервиса и каждого окружения (продакшен и разработка) используется свой ключ, один лишь ключ, которым злоупотребили, говорит, в какой вы строке таблицы. Используйте один ключ везде — и придётся подозревать все места. Как разделять ключи, описано в статье Когда из-за украденного API-ключа блокируют аккаунт OpenAI.

Считайте уязвимости по реально работающей версии

При проверке остальных зависимостей сообщили о «ещё 8 уязвимых зависимостях». Это тоже было неверно. Их посчитали по нижней границе в package.json (диапазон с символом ^). По-настоящему опасными были лишь 2 зафиксированные (pinned) зависимости, оставшиеся позади.

Проверяйте свои реально работающие версии

Смотрите версии, которые на самом деле разрешились, — в lock-файле или в работающем контейнере.

# npm: see what's actually installed
npm ls next react react-dom
 
# inside a running container
docker exec <container> npm ls <package>

Здесь видны версии, которые реально работают, а не ^ из package.json.

Как проверить свою настройку

Заметили ли вы только что странный счёт или ничего ещё не произошло — проверять можно в таком порядке. Каждый шаг касается только вашего собственного аккаунта и сервера.

1

Посмотрите, какой ключ использовался и когда

В панели AI API разбейте использование и расходы по проектам (у Anthropic — по Workspace) и по дням. Использование моделей, которыми вы не пользуетесь, или в часы, когда ничего вашего не работает, означает, что этот ключ утёк. Затем начните со строки таблицы, соответствующей месту хранения этого ключа.
2

Проверьте файлы, отправляемые в браузер

В Next.js значения переменных окружения, начинающихся с NEXT_PUBLIC_, при сборке встраиваются в JavaScript, отправляемый в браузер. После сборки найдите в результатах префикс своего ключа, например grep -rl "sk-" .next/static. Всё, что там нашлось, мог прочитать кто угодно.
3

Убедитесь, что секретные файлы недоступны из публичной папки

Выполните curl -sI https://example.com/.env для своего сайта (замените example.com своим доменом) и убедитесь, что статус в первой строке не 200 (должно быть 404 или 403). Подробнее — в статье проверка секретных файлов в публичных каталогах.
4

Сравните версию в продакшене с бюллетенем

Как во вставке выше, выполните npm ls внутри контейнера продакшена, чтобы увидеть версию. osv-scanner scan -L pnpm-lock.yaml проверяет, есть ли в lock-файле версии с известными уязвимостями.
5

Проверьте логи на секреты

Запустите gitleaks dir --redact для папки, где хранятся логи приложения, чтобы найти ключи, записанные в логи. --redact не выводит на экран сами найденные значения.
6

Проверьте лимиты и оповещения

OpenAI позволяет задать оповещения о расходах и месячный жёсткий лимит расходов на странице лимитов в панели. Anthropic позволяет задать лимит расходов для каждого Workspace. Убедитесь, что у вас не только оповещения, которые ничего не останавливают.

Что сделали в первую очередь (воспроизводимые шаги)

1

Немедленно отозвать ключ, которым злоупотребляли

Это останавливает ущерб, но это лишь часть работы.
2

Обновить фреймворк до исправленной версии

Реально закрыть RCE. После обновления убедиться, что сигнатура утечки исчезла из логов.
3

Сменить каждый секрет

Считая утёкшими. По порядку «что можно использовать откуда угодно — в первую очередь»: ключи объектного хранилища → ключ подписи сессий → API-ключи → учётные данные БД.
4

Разбор взлома

Проверить на закрепление, посторонний cron, исходящий C2 (здесь ничего не было).
5

Защита на стороне аккаунта

Сменить пароль, включить MFA, проверить на незнакомые API-ключи.

Профилактика: автоматический мониторинг CVE

Если свести к сути, этот инцидент был «человек не заметил опубликованную CVSS 10.0». Ручные проверки всегда что-то пропускают. Автоматический мониторинг это находит.

Сканирование зависимостей, которое можно начать сегодня

Бесплатно для старта — один шаг в CI.

# OSV scanner (Google) checks your lockfile
osv-scanner scan -L pnpm-lock.yaml
 
# On GitHub, enable Dependabot (repo Settings → Security)

Этот сайт, согласно тому же уроку, сам мониторит свои зависимости на предмет CVE — мы практикуем то, что советуем.

Частые ошибки и как их исправить

  • Новый ключ кладут на сервер, который всё ещё открыт: тот же путь крадёт и новый ключ. Сразу остановите ключ, которым злоупотребили, устраните уязвимость и уже затем завершите замену остальных секретов.
  • Оповещение о бюджете принимают за ограничение: Google Cloud прямо указывает, что бюджет только с оповещениями не останавливает автоматически использование или расходы. Если нужно, чтобы оно останавливалось, добавьте лимит, блокирующий использование, например месячный жёсткий лимит OpenAI или лимит расходов на Workspace у Anthropic.
  • Ключ AI API кладут в браузер или мобильное приложение: любое значение, попавшее на устройство пользователя, можно извлечь. Вызывайте AI API со своего сервера, а браузер пусть обращается к вашему серверу.
  • Удалили из файлов или истории Git и на этом остановились: тот, кто уже скопировал ключ, всё ещё может им пользоваться. Сначала отзовите, историю чистите потом (см. почему утёкшие на GitHub учётные данные остаются действительными).
  • Один и тот же ключ в разработке и в продакшене: нельзя понять, с какой стороны утечка, и приходится останавливать оба. Выпускайте отдельный ключ для каждого окружения.

Публичный пример: переменные окружения утекли со стороны провайдера

Утечки, которые не найти никаким поиском по файлам, случаются и за пределами вашего сервера. 19 апреля 2026 года Vercel сообщила о несанкционированном доступе к своим внутренним системам. По данным компании, компрометация стороннего ИИ-инструмента, которым пользовался сотрудник, привела к краже части переменных окружения клиентов, не помеченных как «sensitive» (настройка, при которой значение нельзя просмотреть после сохранения).

Vercel посоветовала клиентам проверить и сменить переменные окружения, не помеченные как sensitive, а также проверить журналы активности и недавние развёртывания. Компания также указала, что удаления проектов или аккаунта недостаточно, чтобы устранить риск.

Клиенты в такой ситуации могли бы сколько угодно искать в своём коде и Git и не найти следов утечки. Действие то же, что и в этом случае: заменить каждый секрет, хранившийся в этом месте. Порядок действий — в материале инцидент безопасности Vercel (апрель 2026) (на английском).

Уроки

Частые ошибки

  • Считать делом сделанным на «остановили списания»
  • Расслабляться, потому что grep чистый
  • Маскировать симптом на прокси и чувствовать, что исправлено
  • Считать уязвимости по нижней границе package.json
  • Менять лишь один ключ, по которому подтверждено злоупотребление

Правильная защита

  • Считать остановку списаний и закрытие пути утечки двумя разными задачами и делать обе
  • Подозревать и утечки в среде выполнения (RCE, заголовки)
  • Обновлять фреймворк, который стал причиной
  • Судить по реально работающей версии
  • Заменять всё окружение, если оно утекло

Итого: скачок счёта был симптомом, а причиной была RCE с CVSS 10.0, оставленная без исправления. Верную причину нашли не с первой попытки, а после нескольких ошибок и повторных проверок.

Читать дальше

Источники

  • OWASP, «Secrets Management Cheat Sheet» (риски переменных окружения, реагирование на утечки): cheatsheetseries.owasp.org
  • Документация Next.js, «How to use environment variables in Next.js» (встраивание NEXT_PUBLIC_): nextjs.org
  • OSV-Scanner, «Usage»: google.github.io
  • OpenAI, «Production best practices» (обращение с ключами, оповещения и лимиты): developers.openai.com
  • Claude Help Center, «Creating and managing Workspaces» (лимиты и ключи Workspace): support.claude.com
  • Google Cloud, «Create, edit, or delete budgets and budget alerts»: docs.cloud.google.com
  • Vercel, «Vercel April 2026 security incident»: vercel.com

FAQ

QЕсли утёк API-ключ, достаточно ли отозвать только тот ключ, которым злоупотребляли?
A

Нет. Когда путь утечки лежит в среде выполнения (RCE или утечка через заголовки), исходите из того, что разом утекли все секреты окружения, и меняйте их все. Ключ, злоупотребление которым вы заметили, — лишь малая часть того, что утекло.

QЕсли grep по коду и по git не находит ключей, я в безопасности?
A

Не обязательно. Даже если ни в одном файле нет ключа, уязвимость фреймворка может выкачать переменные окружения прямо из работающего процесса. Утечки случаются и в среде выполнения, а не только в файлах.

QЧто надёжнее всего предотвратит повторение?
A

Машинный мониторинг ваших зависимостей на предмет CVE. Причиной здесь был человек, месяцами не замечавший опубликованную CVSS 10.0; Dependabot или osv-scanner находят такое автоматически.

QСчёт за AI API внезапно вырос. Что делать в первую очередь?
A

Сначала отключите ключ, которым злоупотребляют, в панели провайдера, чтобы остановить ущерб. Затем сузьте путь утечки по тому, где хранился этот ключ, устраните уязвимость или ошибку настройки и только после этого замените все секреты, хранившиеся в том же месте. Если только отозвать ключ, путь утечки останется открытым.

QКак узнать, какой ключ утёк?
A

В панели AI API разбейте использование и расходы по проектам (у Anthropic — по Workspace). Ключ, за которым стоит использование моделей или часов, которыми вы никогда не пользуетесь, и есть утёкший. Если вы держите отдельные ключи для каждого сервиса и окружения, место хранения этого ключа также подскажет путь утечки.

QЕсли настроить оповещение о бюджете, мошеннические списания прекратятся?
A

Не обязательно. Google Cloud, например, прямо указывает, что бюджет только с оповещениями не ограничивает автоматически использование или расходы. Чтобы действительно остановить использование, установите лимит, который его блокирует, например месячный жёсткий лимит расходов OpenAI или лимит расходов на Workspace у Anthropic.