# теория · шаг 2 из 5
Что утекает из корня сайта
Коротко
Каталог сайта - это не только то, что ты собирался показывать. Рядом лежат служебные файлы, и без двух строк они отдаются как обычная статика.
- Замер на голом конфиге:
.env,.git/HEADиdump.sql- все 200 с содержимым. С двумя запрещающими блоками - все 403. autoindex onв каталоге без индекса выкладывает список файлов целиком.server_tokens offубирает версию, но не словоnginx.- Проверяется всё это одним списком curl - и делать это надо на чужих конфигах, которые принимаешь в поддержку.
Если помнишь про location ~ /\. и знаешь, что именно скрывает server_tokens - листай до «Теперь сам».
Сначала ответь сам
Сайт выложен из репозитория: git clone, настроили nginx, root указывает на
каталог проекта. Что можно скачать снаружи?
for p in /.env /.git/HEAD /dump.sql /files/; do printf '%-12s ' $p; curl -s http://shop.local$p | head -1; done
/.env DB_PASS=secret
/.git/HEAD ref: refs/heads/main
/dump.sql backup
/files/ <html> ... (список файлов при autoindex)
Всё. Пароль к базе, служебные файлы репозитория, дамп и листинг каталога. nginx не виноват: его попросили отдавать каталог, он и отдаёт - в нём нет понятия «служебный файл».
На что это похоже
Переезд в новый офис: коробки внесли в приёмную и открыли двери для клиентов. В коробках вперемешку каталог продукции, бухгалтерия и связка запасных ключей. Никто не прятал бухгалтерию специально - просто выставили всё, что привезли.
Каталог развёртывания устроен так же: в нём лежит и то, что показывают, и то, чем это собрали.
Механизм: что попадает в каталог сайта
Правило
location ~ /\. { deny all; }
location ~* \.(bak|old|sql|log|ini|sh|swp)$ { deny all; }
for p in /.env /.git/HEAD /dump.sql /; do printf '%-12s ' $p; curl -s -o /dev/null -w '%{http_code}\n' http://shop.local$p; done
/.env 403
/.git/HEAD 403
/dump.sql 403
/ 200
/files/ эти два блока не закрывают - листинг каталога отключают отдельно,
об этом ниже. Первая строка закрывает всё, что начинается с точки в любом сегменте пути:
.env, .git, .htaccess, редакторские .swp. Вторая - резервные копии и
дампы по расширению.
Исключение из первой строки понадобится ровно одно - для ACME:
location ^~ /.well-known/acme-challenge/ { root /var/www/certbot; }
location ~ /\. { deny all; }
Модификатор ^~ отменяет проверку регулярок (четвёртый раздел), поэтому
запрещающий блок до челленджа не доберётся. Без этой пары продление сертификата
сломается - и разбор будет долгим, потому что причина в блоке, который писали
ради безопасности.
Разбор: листинг каталога
autoindex on;
Директива по умолчанию off, и включают её осознанно - обычно для внутреннего
файлового обмена. Беда в том, что «внутренний» со временем становится публичным:
каталог остаётся, ограничение по адресу теряется при переезде, а список файлов
рассказывает и про имена, и про размеры, и про даты.
Если листинг нужен, его закрывают вместе с каталогом:
autoindex on;
allow 192.168.1.0/24;
deny all;
Отдельная тонкость: каталог без index.html и без autoindex отдаёт 403, а
не 404. По этому 403 сканер понимает, что каталог существует.
Разбор: что рассказывает о себе сервер
Server: nginx/1.31.5
Server: nginx
server_tokens off убирает версию, но не слово nginx: полностью скрыть
продукт штатными средствами нельзя (для этого нужен сторонний модуль или
пересборка). Смысл настройки скромный: сканер всё равно опознает сервер по
поведению, но массовый перебор «уязвимых версий по баннеру» мимо тебя пройдёт.
Заодно версия исчезает и со страниц ошибок - там она печатается в подвале.
Что ломается без этого
Заголовки безопасности, которых «нет». X-Frame-Options,
X-Content-Type-Options, Content-Security-Policy ставят через add_header -
и они пропадают в любом дочернем location, где есть свой add_header (правило
из второго раздела). Классическая картина: заголовки стоят на уровне server,
проверяются на главной, а на /api/ их нет.
for p in / /api/health /assets/app.js; do
echo "$p: $(curl -sI https://shop.local$p | grep -ci x-content-type-options)"
done
Три единицы - хорошо. Ноль хотя бы в одной строке - ищи add_header уровнем
ниже.
Служебное, доехавшее до интернета. stub_status, панели метрик, /debug
фреймворка, страница профайлера. Всё это закрывают allow/deny или
auth_basic, а лучше - вообще не публикуют на том же адресе.
Зачем это в работе
Список проверок для чужого конфига, который принимаешь в поддержку. Занимает минуту, а находит почти всегда:
for p in /.env /.git/HEAD /.git/config /dump.sql /config.php.bak \
/uploads../etc/passwd /files/ /server-status /stub_status; do
printf '%-28s %s\n' "$p" "$(curl -s -o /dev/null -w '%{http_code}' https://shop.local$p)"
done
Любая двухсотка в этом списке - находка. Дальше по важности: .env и .git
означают компрометацию (пароли и весь код), дамп - утечку данных, листинг -
разведку.
И помни про порядок действий после находки: сначала закрыть, потом считать
утёкшим всё, что было доступно, - менять пароли и перевыпускать ключи. Файл,
который отдавался месяц, наверняка уже скачан: .git собирают автоматом
готовыми утилитами.
Теперь сам
Закрыли .env и .git двумя строками, проверили - 403. Через неделю выясняется,
что кто-то скачал репозиторий целиком. Как это возможно?
Скорее всего, его скачали до того, как поставили запрет: 403 сегодня ничего
не говорит о вчера. Проверить можно по access_log - искать запросы к
/.git/ за прошлые месяцы; сканеры ходят по ним постоянно, и в логе это видно.
Второй вариант - копия репозитория лежит по другому адресу: в бэкапе, в
поддомене со старой версией сайта, в архиве site.zip в том же каталоге.
Главное
В каталоге сайта рядом с публичными файлами лежат служебные, и без запрета nginx
отдаёт их как обычную статику - замер даёт 200 с содержимым .env, .git/HEAD и
dump.sql. Закрывают их двумя блоками: location ~ /\. и запрет по расширениям,
не забыв исключение ^~ /.well-known/acme-challenge/. autoindex включают
осознанно и вместе с ограничением по адресу; server_tokens off убирает версию,
но не слово nginx. Заголовки безопасности проверяют на нескольких маршрутах -
свой add_header в дочернем блоке уносит их целиком. А найденную утечку считают
состоявшейся: пароли меняют, ключи перевыпускают.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий