Неверная настройка, которая реально случается на шаред-хостинге, с убранными доменами и значениями и превращённая в урок. Это не про поиск чужих сайтов — это про защиту своего (или унаследованного) развёртывания.
- Класс
- Открытость .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.json | OAuth 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 считайте секреты уже увиденными. По приоритету:
- Внешние API-ключи (высший приоритет, немедленно): они завязаны на биллинг и захват аккаунта. Отзовите + перевыпустите OAuth
CLIENT_SECRET, API-ключи и облачные учётные данные, с минимальными привилегиями. - OAuth client secrets.
APP_KEY: перегенерация может сломать сессии и сделать зашифрованные столбцы БД нерасшифровываемыми — сначала проверьте, на что это повлияет.- Почта/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 снаружи = недостижимы
Проблемы, с которыми мы столкнулись при исправлении
- Проблема области поддомена: перенацеливание 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-ключи
Читать дальше
- Глоссарий: Что такое .env · Что такое CVE
- Защита: Держим .env вне публичного веба на шаред-хостинге
- Инцидент: Когда украденный API-ключ привёл к мошенническим счетам (другой путь утечки — среда выполнения)
- Фреймворк: Безопасность Laravel (открытость .env, APP_DEBUG и авторизация — все три сразу)