Открытые файлы .env и утёкшие ключи

Открытый файл .env — это конфигурационный файл, тот самый, что хранит пароль от вашей базы данных, почтовые учётные данные и API-ключи, который ваш веб-сервер отдаёт любому, кто попросит его по имени, а открытая папка .git — та же авария со всей историей вашего кода в придачу. Ни то ни другое не требует от злоумышленника что-либо взламывать: файл просто лежит там, где сервер публикует, и один обычный GET-запрос его скачивает. В этой статье объясняется, как это происходит — ошибки корневого каталога документов, статические хосты, отдающие сам репозиторий, артефакты сборки, которые никто не проверял, — что нашедший делает с ключами в первый час и почему удаление файла после этого — самая маленькая часть ответа. Чтобы проверить свой сайт прямо сейчас, запустите сканер утёкших секретов и читайте со своими результатами.

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

У каждого веб-сервера есть корневой каталог документов: каталог, содержимое которого публично по определению. Всё, что в него помещено — намеренно или случайно, — можно запросить по URL, независимо от того, ссылается ли на него какая-либо страница. Классическая авария — проект, развёрнутый копированием всего рабочего каталога на сервер: код приложения, которому там самое место, прибывает вместе с файлом .env рядом с ним, папкой .git внутри него, резервными копиями редактора, дампом базы данных, который кто-то сделал перед миграцией, и архивом прошлогодней версии. Сервер не может их различить. Он отдаёт файлы; частные — тоже файлы.

Соглашения фреймворков делают ловушку лёгкой. Файл .env должен находиться уровнем выше корневого каталога документов или вовсе вне развёрнутого дерева, а приложение должно читать его с диска — никогда через веб-сервер. Но серверы разработки охотно отдают корень проекта, маленькие развёртывания тащат всё в public_html, а конфигурация, скопированная из учебника, может направить корневой каталог на сам проект. Ничего заметно не ломается, когда это происходит, и в этом ровно и проблема: сайт работает идеально, файл публичен, а первым это замечает тот, кто запрашивает /.env, — запрос, который автоматизированные сканеры непрерывно посылают каждому сайту в интернете.

Статические хосты, отдаваемые репозитории и артефакты сборки

Статический хостинг убирает конфигурацию сервера из уравнения и заменяет её более простым правилом: что находится в каталоге публикации, то и публикуется. Разверните репозиторий на статическом хосте с выходным каталогом, установленным в корень проекта, и хост верно отдаст репозиторий — включая .git, если он был скопирован в вывод. Открытый .git хуже, чем кажется: файл HEAD и конфигурация доказывают структуру, объектные файлы содержат каждый когда-либо зафиксированный blob, и обычными инструментами Git вся история может быть восстановлена — включая секреты, однажды зафиксированные и «удалённые» в более позднем коммите, что удаляет их из рабочего дерева и больше ниоткуда.

Артефакты сборки создают второй, более тихий класс раскрытий. Сборщики JavaScript встраивают то, на что ссылается код, поэтому API-ключ, прочитанный из переменной окружения во время сборки, может оказаться литеральной строкой внутри отправленного пакета — публичный по замыслу, читаемый каждым посетителем и никогда не отмеченный никакой проверкой разрешений, потому что этот файл и должен был быть публичным. Карты исходников усугубляют это, отправляя оригинальный исходный код рядом с пакетом. Ничего из этого не требует неверно настроенного сервера; нужен лишь билд, который никогда не проверяли снаружи.

Третий паттерн — остатки: скрипты развёртывания, которые распаковывают архив в корневой веб-каталог и оставляют архив там, плагины резервного копирования, пишущие дампы в папку, доступную через веб, config.php.bak, сохранённый редактором, — который многие серверы отдают как обычный текст именно потому, что расширение больше не соответствует обработчику PHP. Каждый остаток — полная копия чего-то частного по предсказуемому адресу.

Что злоумышленники делают в первый час

Обнаружение автоматизировано и постоянно. Сканеры перечисляют известные пути — файлы env, внутренности Git, имена конфигураций, расширения резервных копий — по огромным диапазонам адресов, и свежее раскрытие обычно находится в течение часов, часто быстрее. На ваш сервер приходит ничем не примечательный GET-запрос, возвращающий 200; тревоги нет, потому что с точки зрения сервера не произошло ничего необычного. Поэтому исходите из того, что любой файл, бывший публичным один день, был скачан, и что бремя доказательства идёт в обратную сторону.

Сортировка тоже автоматизирована. Скачанный .env разбирается за секунды: учётные данные базы данных проверяются на любом достижимом порте базы данных, облачные ключи проверяются безвредным вызовом API, который говорит, живы ли они, почтовые учётные данные пробуются для отправки, а платёжные ключи проверяются по режиму — платёжный ключ в живом режиме и производственные учётные данные базы данных — главный приз, потому что каждый напрямую превращается в деньги или данные. Действительные ключи быстро используются, продаются или удерживаются, потому что их владельцы могут в любой момент их ротировать; первый час — это когда открытый ключ стоит дороже всего, и он тратится эффективно.

Почему удаления файла недостаточно

Удаление файла прекращает новые загрузки. Оно ничего не делает с копиями, которые уже существуют: с копией нашедшего, очевидно, но также с теми, что вы сделали сами, — с ответом, кэшированным вашей CDN или корпоративным прокси, со снимком в обходе поисковой системы или архивной службы, с пакетом, который браузер уже скачал. Пока каждый кэш не очищен и не истёк, «удаённый» файл всё ещё может отдаваться инфраструктурой, которую вы контролируете, но забыли очистить, а версия вне вашего контроля находится вне его навсегда.

Вот почему первый ответ на подтверждённое раскрытие — ротация, а не удаление: отзовите раскрытые учётные данные и выпустите новые, чтобы каждая копия в мире обесценилась в один и тот же момент. У ротации есть порядок — выпустить замену, развернуть её, проверить, что служба работает, затем отозвать старые учётные данные — и она распространяется на всё, что раскрытый материал может отпереть: раскрытие .git означает ротацию каждого секрета, который когда-либо появлялся в истории, а не только текущих, потому что раскрыта сама история. После этого читайте журналы доступа для раскрытых путей: они говорят вам, были ли вы первым, кто узнал, а более ранние получения превращают инцидент конфигурации в расследование нарушения с совсем другим перечнем.

Список опроса, объяснён — и перечень профилактики

Сканер, такой как сканер утёкших секретов, может запрашивать файлы только по имени, поэтому его список — известное множество: /.env и его варианты окружения, /.git/HEAD и /.git/config, знакомые имена конфигурационных файлов, расширения резервных копий и архивов и имена файлов ключей — плюс собственные скрипты страницы, в которых ищутся строки в форме ключей. Честное ограничение режет в обе стороны: сканер находит только то, что запрашивает, и поэтому подтверждает находку по содержимому, а не по коду состояния. Ответ 200, возвращающий вашу главную страницу, — это универсальный маршрут, а не утечка, и любой отчёт, считающий его утечкой, раздувает сам себя. Связанный риск цепочки поставок — скрипты, которые вы намеренно загружаете у других, — рассматривается в статье сторонние скрипты и риск цепочки поставок.

Профилактика — это в основном размещение и один слой отказа. Держите секреты вне корневого каталога документов и вне публикуемого вывода репозитория; заставьте сервер отказывать опасным именам, даже если они туда попадут, — правила запрета для файлов с точкой, расширений env и конфигураций, архивов и материала ключей, проверенные их запросом; удаляйте .git из артефактов развёртывания или блокируйте его outright. В конвейере добавьте сканирование секретов в CI — на коммитах, чтобы ключ останавливался до отправки, и на построенном артефакте, чтобы упакованный ключ был пойман в выводе, а не незнакомцем. Наконец, сканируйте сами по расписанию и после каждого изменения развёртывания: раскрытия в этой статье все бесшумны, и единственный способ узнать о своём раньше других — задать своему серверу те же вопросы, что задают сканеры.

Источники и дальнейшее чтение

Частые вопросы

Я удалил свой открытый файл .env. Я теперь в безопасности?

Ещё нет. Удаление прекращает новые загрузки, но копии могут существовать в руках нашедшего, в кэшах CDN и прокси, а также в обходах и архивах. Ротируйте каждые учётные данные, которые содержал файл, — отзовите старые ключи и выпустите новые, — очистите свои кэши и проверьте журналы доступа для этого пути. Только ротация обесценивает копии.

Мой сайт возвращает 200 для /.env. Файл раскрыт?

Не обязательно. Многие сайты отвечают на каждый неизвестный путь кодом 200 и своей главной страницей или оболочкой приложения — универсальный запасной вариант. Важно содержимое: если тело ответа — ваша страница, ничего не раскрыто; если это строки «ключ–значение» с настоящими значениями — раскрыто. Надёжный сканер подтверждает по содержимому и перечисляет голые 200 отдельно как неподтверждённые.

Что должно выполняться в CI, чтобы это не повторилось?

Две проверки в два момента: сканер секретов на коммите, который не даёт ключу войти в репозиторий, и проверка построенного артефакта и развёрнутого выходного каталога, которая ловит то, что сборка встроила, и то, что развёртывание скопировало. Добавьте серверные правила запрета для файлов с точкой и материала ключей как последний слой и повторно запускайте внешний опрос после изменений развёртывания.