Перейти к содержимому
>_ITDITDПлатформа веб-безопасности

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

У Laravel-приложений .env был доступен всему миру — самая частая ошибка шаред-хостинга

На шаред-хостинге у Laravel-приложений .env, .git, дампы БД и OAuth-секреты оказываются читаемыми по HTTP. Причина: весь проект под веб-корнем вместо только public/. Исправление в три шага и почему это общеотраслевой дурной паттерн.

Опубликовано 2026-06-07 6 мин чтения

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

Карточка инцидента — INCIDENT FILE
Класс
Открытость .env / .git / дампов БД (неверная настройка)
Серьёзность
Критическая (каждый секрет читаем по HTTP)
Причина
Тело Laravel размещено прямо под public_html (выставляться должен только public/)
Распространение
Каждое новое приложение копировало ту же дыру → десятки
Постоянное решение
Тело вне docroot + симлинк только на public/

Худшие файлы — .env и .git

.env — это связка ключей: аутентификация БД, почта, внешние API, ключ шифрования приложения (APP_KEY). При публичном .git злоумышленники восстанавливают не только текущие значения, но и каждую прошлую ротацию, пройдя по истории git. См. Что такое .env.

Что происходило

На шаред-хосте многие Laravel-приложения были разложены так. Вот опасная и правильная раскладки рядом:

Опасная раскладка (частая)

~/public_html/
└── app/          ← весь Laravel (неверно)
    ├── .env      ← читается по /app/.env
    ├── .git/     ← clone восстанавливает всю историю
    ├── vendor/  config/  storage/
    └── public/   ← ЕДИНСТВЕННОЕ, что должно тут быть

Правильная раскладка

~/laravel/app/    ← тело вне веб-корня
├── .env  .git/   ← недостижимо по HTTP = безопасно
└── public/       ← только это, выставлено через симлинк
        └── index.php

Типичные файлы, которые возвращали 200 OK внешнему миру:

ФайлЧем опасен
.env / .env.bk_*аутентификация БД, почта, внешние API-ключи, APP_KEY — всё в открытом виде
директория .git/git clone восстанавливает исходник и всю историю
composer.lock / package-lock.jsonточные версии зависимостей = список целей для известных CVE
credentials.jsonOAuth client secrets, в комплекте
*.sql / db-*.sql.gzполный дамп базы данных, десятки МБ в сжатом виде

Самое страшное: распространение

Раз разложив так, каждое новое приложение того же рода копирует ту же дыру. Одна неверная настройка превращается в десятки. Правило: в момент, как заметили, проверьте их все.

Исправление в три шага

Шаг 1 — Первая помощь: заблокируйте чувствительные файлы на уровне HTTP

Сначала остановите «дальнейшую утечку». Это обратимо и не влияет на обычные страницы. Добавьте блок deny в начало public_html/.htaccess (защитный пример конфигурации):

# === SECURITY BLOCK ===
# 404 на всё под .env / .git
RedirectMatch 404 (?i)/\.(env|git)(\..*)?(/|$)
 
# запретить резервные копии, дампы, файлы учётных данных
<FilesMatch "(?i)\.(sql|sql\.gz|bak|old|swp|save|orig|tgz)$|^credentials\.json$|\.bk[._]">
    Require all denied
</FilesMatch>
 
# если внутренности Laravel видны на /<app>/storage/... и т. п.
RedirectMatch 403 (?i)^/[^/]+/(storage|bootstrap/cache|config|database|resources|routes|tests|vendor)(/|$)
# === END ===

Но чёрный список по сути — игра в «прибей крота». Вы забудете composer.lock; новые имена файлов добавляют новые дыры. Относитесь к нему как к первой помощи по мере сил, а настоящее исправление — в Шаге 3.

Шаг 2 — Смените ключи (считайте, что их видели)

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

  1. Внешние API-ключи (высший приоритет, немедленно): они завязаны на биллинг и захват аккаунта. Отзовите + перевыпустите OAuth CLIENT_SECRET, API-ключи и облачные учётные данные, с минимальными привилегиями.
  2. OAuth client secrets.
  3. APP_KEY: перегенерация может сломать сессии и сделать зашифрованные столбцы БД нерасшифровываемыми — сначала проверьте радиус поражения.
  4. Почта/SMTP, учётные данные БД.

Ловушка ротации (реально встреченная на практике)

У некоторых OAuth-провайдеров перевыпуск client secret также аннулирует существующие refresh_token — обновление по сохранённым токенам падает и вызывает простой продакшена. Всегда держите готовым поток повторной авторизации до перевыпуска. И обновите свой локальный .env / конфигурацию тоже, иначе следующий деплой вернёт старое значение.

Шаг 3 — Реструктуризация: вынесите тело вне docroot (настоящее исправление)

Переразложите каждое приложение так:

# 1. Вынести тело вне веб-корня (та же файловая система → mv мгновенный, атомарный)
mv ~/example/public_html/app  ~/example/app-laravel
 
# 2. Поместить в веб-корень лишь минимальный бутстрап
mkdir -p ~/example/public_html/sub
cat > ~/example/public_html/sub/index.php <<'PHP'
<?php require __DIR__ . '/../../app-laravel/public/index.php';
PHP
# 3. Скопировать Laravel public/.htaccess, сделать симлинки на статику, очистить кэш конфигурации

В этой форме .env, .git и vendor/ просто не существуют в веб-корне, так что вы в безопасности без опоры на правила .htaccess. Полные шаги — в Держим .env вне публичного веба на шаред-хостинге.

✗ Опасно: тело внутри веб-корня

— граница веба (читаемо по HTTP) —

.env · .git · vendor · public

↑ всё внутри линии = полностью открыто

✓ Безопасно: тело вне веб-корня

— граница веба (читаемо по HTTP) —

только public/ (симлинк)

.env · .git снаружи = недостижимы

Одна линия всё решает — внутри или снаружи веб-корня. Если .env и .git сидят внутри, их может прочитать кто угодно по HTTP.

Ловушки, которых «нет в учебнике»

  • Проблема области поддомена: перенацеливание docroot зарегистрированного поддомена на другое дерево через симлинк может не последовать и вернуть 500. → Держите docroot реальной директорией и require по абсолютному пути из маленького index.php внутри неё (форма «бутстрап-редирект») для устойчивости.
  • opcache помнит сломанное состояние: opcache может запомнить неудачную загрузку index.php и продолжать возвращать 500 даже после пересборки. Крайнее средство: сменить имя файла (entry.php и т. п.).
  • «Незарегистрированный» docroot поддомена, читаемый через родителя: https://sub.example/ может быть недействителен, тогда как https://example/sub/ всё ещё показывает содержимое — легко упускаемый вход. При проверке блокируйте и путь родительского домена.
  • Захардкоженные секреты в config/*.php: в сочетании с публичным .git история раскрывает каждую ротацию. Всегда идите через env() и никогда не кладите реальные значения в значения по умолчанию.

Главный урок: это не «ошибка одного человека»

Ранняя культура шаред-хостинга клала всё прямо под public_html. Laravel (который ожидает выставленным только public/) влился в это, и опасная раскладка «проект под public_html/<app>/, выставить его public/» стала де-факто стандартом — собственные документы вендоров используют примеры на базе public_html, что делает только хуже. Поэтому исправляйте это процессом, а не бдительностью:

Профилактика рецидива процессом

  • Сделайте дефолтом развёртывания «тело вне docroot, симлинк только на public/».
  • Проверки CI/lint: никогда не коммитить .env; никогда не размещать проект прямо под public_html/<app>.
  • Самопроверка после деплоя: убедиться, что /.env и /.git/config не получаемы снаружи.

Этот сайт встраивает привычку «проверь свой сайт сам» в свой обучающий трек. → Чем опасны .env и API-ключи

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

FAQ

QМой .env открыт — что делать в первую очередь?
A

Остановите кровотечение (заблокируйте путь через .htaccess), затем смените внешние API-ключи и OAuth-секреты в .env по приоритету (считайте, что их видели), затем перенесите тело приложения вне веб-корня, чтобы быть в безопасности без опоры на правила.

QПочему размещать Laravel под public_html опасно?
A

Laravel построен так, чтобы выставлять только public/. Поместите весь проект в docroot — и всё выше него: .env (все ваши секреты), .git (восстановимая история), vendor/ — становится читаемым по HTTP.

QДостаточно ли блокировки чёрным списком в .htaccess?
A

Как первая помощь работает, но по сути это игра в «прибей крота» — каждое новое имя файла добавляет дыру. Настоящее исправление — структурное изменение: вынести тело приложения вне docroot и выставлять только public/ через симлинк.