Защищённый веб-сервер Nginx с ограниченными сетевыми входами и модулями
DevOps

Как сократить поверхность атаки Nginx

Проверяем сборку и модули Nginx, закрываем неизвестные домены и служебные файлы, ограничиваем методы и права, тестируем конфигурацию без бесполезной маскировки версии.

Содержание

Поверхность атаки — всё, с чем может взаимодействовать внешний клиент: открытые порты, виртуальные хосты, маршруты, модули, методы, служебные файлы и приложение за Nginx. Чем меньше лишних точек входа, тем проще понимать и проверять поведение сервера.

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

Узнайте, какой Nginx работает

Проверьте путь к бинарному файлу, параметры сборки и источник пакета. Команда nginx -V выводит версию, криптографическую библиотеку и configure arguments в поток ошибок, поэтому используется перенаправление 2>&1:

command -v nginx
nginx -V 2>&1
systemctl status nginx --no-pager

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

Убедитесь, что пакет получает security updates из доверенного репозитория. Скрытый номер версии не защищает устаревший бинарный файл.

Проверьте открытые порты и владельцев

Nginx обычно должен слушать только HTTP/HTTPS, а приложение — локальный адрес или Unix-сокет. Следующие команды показывают TCP-порты и привязанные процессы, а затем активные правила UFW:

sudo ss -ltnp
sudo ufw status verbose

Если внутреннее приложение слушает 0.0.0.0:3000, его можно обойти мимо Nginx при разрешающем firewall. Переведите его на 127.0.0.1:3000, проверьте локальный запрос и только затем закройте публичный порт. Не удаляйте правило SSH во время удалённой сессии.

Не показывайте production-сайт на неизвестном домене

Для каждой пары адреса и порта задайте явный default server. Следующий HTTP-блок возвращает 404 любому имени, которое не совпало с вашими виртуальными хостами:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 404;
}

Параметр default_server, а не символ _, назначает этот блок запасным. Для HTTPS нужен отдельный default server и решение о сертификате: TLS начинается до чтения HTTP Host. Не копируйте случайный сертификат реального сайта в обработчик чужих имён без понимания результата.

Проверьте неизвестный Host отдельно. Он не должен раскрывать главную страницу, редиректить на значение из входного заголовка или попадать в приложение.

Публикуйте только подготовленный каталог

В root должны лежать файлы, предназначенные для выдачи: HTML, стили, скрипты, изображения и публичные документы. Репозиторий .git, исходные .env, дампы базы, журналы и резервные копии храните вне этого дерева.

Такое разделение надёжнее бесконечного списка запрещённых расширений. Новый тип резервной копии не станет публичным только потому, что его забыли добавить в regex.

Для дополнительной защиты закройте скрытые файлы, сохранив доступ к каталогу ACME challenge. Правило располагается внутри нужного server и требует теста на вашей версии PCRE:

location ~ /\.(?!well-known/) {
    deny all;
}

Отрицательная проверка исключает путь /.well-known/. Не полагайтесь на неё как на единственный барьер: .env всё равно не должен находиться в публичном каталоге. После изменения проверьте выдачу сертификата и запросы к нескольким скрытым именам.

Запретите листинг каталогов

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

location /downloads/ {
    autoindex off;
    try_files $uri =404;
}

При запросе каталога без index-файла посетитель не должен получить перечень содержимого. Если список загрузок нужен продукту, формируйте контролируемую страницу с разрешёнными файлами, а не открывайте всё дерево.

Ограничьте методы только там, где это верно

Статический каталог обычно обслуживает GET и HEAD. В нём можно отклонить другие методы через limit_except:

location /assets/ {
    limit_except GET HEAD {
        deny all;
    }

    try_files $uri =404;
}

Не переносите правило на весь сервер без проверки. Формам нужен POST, CORS preflight может использовать OPTIONS, а API — PUT или DELETE. Nginx ограничивает сетевой вход, но само приложение всё равно должно проверять метод, права и входные данные.

Уберите ненужные служебные endpoints

Страница stub_status, метрики, health-check и административный маршрут полезны для эксплуатации, но не обязаны быть доступны всему интернету. Привяжите их к локальному или внутреннему listener, ограничьте сеть и не выводите секреты.

Проверка здоровья должна сообщать минимум, необходимый балансировщику. Версии библиотек, переменные окружения, пути и stack trace оставляйте в защищённом журнале.

Server tokens — косметическая мера

Следующая директива в контексте http, server или location убирает номер версии Nginx из поля Server и стандартных страниц ошибок:

server_tokens off;

Это уменьшает лишнюю информацию, но не скрывает сам тип сервера надёжно и не мешает проверять известную уязвимость. Не отмечайте задачу выполненной, пока пакет не обновлён и лишние маршруты остаются открытыми.

Проверьте права на конфигурацию и ключи

Master-процессу нужны права читать конфигурацию и TLS-ключи, worker-процессу — читать публичные файлы и писать только в необходимые каталоги кеша или временных данных. Не делайте весь /var/www доступным на запись www-data.

Проверьте путь к главному файлу и ключу без изменения прав:

namei -l /etc/nginx/nginx.conf
sudo namei -l /etc/letsencrypt/live/example.com/privkey.pem
sudo -u www-data test -r /var/www/example/current/index.html

Последняя команда должна завершиться кодом 0 для публичного файла. Worker обычно не должен читать приватный ключ. Пользователя www-data и пути замените значениями из активной конфигурации.

Проведите отрицательные проверки

После nginx -t и reload проверьте не только главную страницу, но и то, что должно быть недоступно. --resolve направляет тестовое имя на выбранный IP без изменения DNS:

sudo nginx -t
sudo systemctl reload nginx

curl -I --resolve example.com:443:127.0.0.1 https://example.com/
curl -I -H 'Host: unknown.invalid' http://127.0.0.1/
curl -I https://example.com/.env
curl -I -X POST https://example.com/assets/app.css

Ожидайте успешный ответ настоящего сайта и контролируемые 4xx для неизвестного домена, скрытого файла и неподходящего метода. Проверка /.env не доказывает отсутствие других секретов; дополнительно просмотрите состав каталога публикации.

Откат и регулярный пересмотр

При поломке законного маршрута верните только последнее правило, снова выполните nginx -t и reload. Не откатывайте security update ради совместимости со старой директивой: сначала найдите поддерживаемый вариант конфигурации.

После обновления Nginx повторяйте инвентаризацию модулей, портов и полной конфигурации. Новый виртуальный хост, proxy или каталог загрузок меняет поверхность атаки и требует собственных отрицательных тестов.

Права файлов подробно разобраны в статье о доступе Nginx к каталогу сайта, TLS — в руководстве по конфигурации HTTPS, а лимиты — в материале об ограничении запросов.

Первоисточники

Рекламное местоВаша компания здесьРазместить рекламу

Самопроверка

Проверьте, что материал усвоен

Ответьте на все вопросы. Результат сохранится только в этом браузере и будет учтён в статистике прочитанных материалов.

01Что важнее скрытия номера версии Nginx?
02Как должен вести себя default server для неизвестного Host?
03Где лучше хранить исходники и .git относительно web root?

Разбираем коротко

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

Повышает ли server_tokens off безопасность Nginx?

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

Нужно ли запрещать все HTTP-методы кроме GET и HEAD?

Только для маршрута, который действительно является статическим и не принимает другие методы. API, WebDAV, загрузки и формы могут требовать POST, PUT или OPTIONS; ограничение проектируют по функции.

Почему каталог сайта не должен содержать репозиторий и резервные копии?

Ошибка location или MIME-настройки способна отдать посетителю .git, конфигурацию, дамп либо старую копию. Публичный root должен содержать только подготовленные к публикации файлы.