nginx: заголовки безопасности, скрытие версии и настройки TLS

0
11
Обложка статьи: nginx: заголовки безопасности, скрытие версии и настройки TLS

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

Статья опирается на логику направления «Сетевая безопасность» из методики зрелости Группы Сбер (источник); это методика для крупных компаний, мы используем её как ориентир и пересказываем своими словами. Конфигурация проверена на nginx 1.31 в одноразовом контейнере. Версию nginx и параметры сборки проверьте у себя: директива http2 on; поддерживается с версии 1.25.1, в более старых используйте listen 443 ssl http2;. Перед применением на рабочем сервере выполните nginx -t и держите откат: управление изменениями.

Что должно получиться

Уровень Что это значит на практике
1. Базовый На границе стоит межсетевой экран, наружу открыты только нужные порты
2. Гигиенический Сеть сегментирована, правила пересматриваются; публичные сервисы настроены безопасно и защищены от типовых атак

Критерии сформулированы нами по мотивам методики, это ориентир, а не оценка соответствия. Дополнительно: настройка SSL в nginx и WAF и ограничения запросов.

Файл 1. Общие настройки

Сохраните как /etc/nginx/conf.d/security.conf. Он подключается в контекст http. Директива keepalive_timeout здесь намеренно не повторяется: она уже задаётся в основном nginx.conf, и повторное объявление приводит к ошибке при проверке конфигурации.

# /etc/nginx/conf.d/security.conf: общие настройки безопасности (контекст http)
server_tokens off;                          # не показывать версию nginx в заголовках и страницах ошибок
client_max_body_size 10m;                   # ограничение размера тела запроса
client_body_timeout 15s;
client_header_timeout 15s;
send_timeout 15s;

ssl_protocols TLSv1.2 TLSv1.3;              # старые версии протокола отключены
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
  • server_tokens off убирает версию nginx из заголовка Server и страниц ошибок. Версия остаётся в заголовке как просто «nginx»; скрытие версии — не защита, а снижение информативности для сканеров.
  • Ограничения размера тела и таймауты сокращают поверхность для медленных атак; значения подберите под приложение (загрузка файлов требует большего размера).
  • TLS 1.2 и 1.3 разрешены, старые протоколы отключены; перечень шифров оставлен по умолчанию сервера (для современных версий он безопасен): не копируйте длинные списки из старых руководств.
  • ssl_session_tickets off снижает риск повторного использования ключей сеансов.

Файл 2. Заголовки безопасности

Сохраните как /etc/nginx/snippets/headers.conf. Это отдельный файл, который вы подключаете в каждый server и location, где есть свои add_header.

# /etc/nginx/snippets/headers.conf: заголовки безопасности; подключайте в каждом server и location,
# где есть собственные add_header (иначе унаследованные заголовки пропадают)
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
Заголовок Что делает
X-Content-Type-Options: nosniff Запрещает браузеру угадывать тип содержимого
X-Frame-Options: SAMEORIGIN Запрещает встраивать страницы в чужие фреймы (защита от кликджекинга)
Referrer-Policy Ограничивает, сколько адреса уходит в заголовке Referer на другие сайты
Permissions-Policy Отключает ненужные возможности браузера (геолокация, камера, микрофон)

Если на сайте нужны встроенные карты, видео или формы с чужих доменов, заголовки придётся ослабить: проверяйте работу страниц после включения. Заголовок Content-Security-Policy мощнее, но требует тщательной настройки под ваши скрипты, поэтому включайте его отдельно, сначала в режиме отчёта (Content-Security-Policy-Report-Only).

Ловушка add_header: почему заголовки пропадают

В nginx директивы add_header наследуются с внешнего уровня на внутренний только если на внутреннем уровне нет собственных add_header. Если вы задали заголовки в http, а в server добавили, например, HSTS, то заголовки из http перестанут отправляться. Во время проверки выяснилось именно так: при заголовках безопасности в общем файле и HSTS в блоке сервера в ответе остались только HSTS и два заголовка, объявленных рядом, а остальные исчезли без ошибок и предупреждений. Решение, которое сработало: хранить заголовки в отдельном файле и подключать его в каждом месте, где есть свои add_header (директива include). Параметр always добавляет заголовки и к ответам с ошибками (404, 403, 500).

Файл 3. Пример сервера

Пример /etc/nginx/conf.d/site.conf: HTTPS-сервер, перенаправление с HTTP, HSTS, закрытые служебные файлы. Замените имя и пути сертификатов.

# /etc/nginx/conf.d/site.conf: пример сервера (замените пути сертификатов и имя)
server {
    listen 443 ssl;
    http2 on;
    server_name example.com;

    ssl_certificate     /etc/nginx/certs/example.com.crt;
    ssl_certificate_key /etc/nginx/certs/example.com.key;

    # HSTS включайте после проверки, что весь сайт работает по HTTPS (меняется на год)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    # остальные заголовки подключаем файлом: add_header из внешнего уровня не наследуется, если на уровне ниже есть свой
    include /etc/nginx/snippets/headers.conf;

    location / {
        root /usr/share/nginx/html;
        index index.html;
    }

    # закрыть служебные файлы и каталоги
    location ~ /\.(?!well-known) {
        deny all;
    }
}

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}
  • HSTS сообщает браузерам, что сайт всегда нужно открывать по HTTPS. Включайте после проверки, что весь сайт и поддомены работают по HTTPS: откатить его на стороне клиентов нельзя до истечения срока (здесь год). Для начала используйте небольшое значение (например, max-age=300).
  • Закрытие скрытых файлов. Правило запрещает доступ к путям, начинающимся с точки (.env, .git), кроме .well-known (нужен для выпуска сертификатов).
  • Перенаправление. Код 301 с HTTP на HTTPS сохраняет запрошенный адрес.

Применение

sudo mkdir -p /etc/nginx/snippets
sudo nginx -t
sudo systemctl reload nginx

Что должно получиться: nginx -t выводит «syntax is ok» и «test is successful». При ошибке перезапуск не выполняйте: исправьте строку, указанную в сообщении. Перезагрузка (reload) применяет конфигурацию без разрыва соединений.

Проверка результата

curl -sI https://example.com/
curl -sI https://example.com/nope
curl -sI http://example.com/
curl -sI --tlsv1.1 --tls-max 1.1 https://example.com/

Ожидаемый результат на тестовой конфигурации:

Проверка Что должно быть
Ответ 200 Протокол HTTP/2; заголовок server: nginx без версии; в ответе strict-transport-security, x-content-type-options, x-frame-options, referrer-policy, permissions-policy
Ответ 404 (несуществующая страница) Те же заголовки безопасности (благодаря always)
Запрос по HTTP Ответ 301 и Location: https://example.com/...
Запрос по TLS 1.1 Соединение отклонено (curl не получает ответ); TLS 1.2 и 1.3 принимаются
Запрос /.env Ответ 403

Более подробная проверка сертификатов и протоколов: проверка TLS своего сайта.

Когда за nginx стоит приложение или прокси

  • Приложение (PHP, Python, Java) может само добавлять заголовки и раскрывать версию: отключите expose_php в PHP, уберите заголовок X-Powered-By на стороне приложения или прокси (proxy_hide_header X-Powered-By;).
  • Если перед nginx стоит другой прокси (Traefik, балансировщик), то заголовки можно задавать там, но не дублируйте их в двух местах с разными значениями.
  • При проксировании не забывайте о передаче настоящего адреса клиента для журналов и ограничений: настройка модуля realip.

Диагностика

Симптом Причина и что делать
nginx -t: директива duplicate Директива уже объявлена в основном конфиге. Удалите повтор или изменяйте существующее значение
Часть заголовков не приходит Наследование add_header: подключите файл заголовков во все блоки, где есть собственные add_header
Страницы перестали загружать внешние виджеты Заголовки слишком строгие. Ослабьте политику конкретных заголовков и проверьте страницу
Браузер продолжает открывать только HTTPS после отката HSTS HSTS уже закэширован в браузерах на срок max-age. Поэтому начинайте с малого значения
Версия nginx по-прежнему видна Заголовок добавляет другой слой (прокси, контейнер) или модуль. Проверьте ответ на каждом уровне

Где это в модели зрелости

Материал относится к направлению «Сетевая безопасность» (материалы направления, карта зрелости). Уровень 2 требует управляемого периметра и защиты публичных сервисов: безопасная конфигурация веб-сервера — базовая часть такой защиты.

Как проверить

  1. nginx -t проходит, перезагрузка выполнена без ошибок.
  2. Команды curl -sI показывают заголовки безопасности как в 200, так и в 404-ответах, версия nginx скрыта.
  3. HTTP перенаправляется на HTTPS, TLS 1.1 отклоняется, TLS 1.2 и 1.3 работают.
  4. Служебные файлы (.env, .git) недоступны, сайт работает без ошибок в браузере.

Источники

  • Методика оценки уровня зрелости кибербезопасности компании, Группа Сбер: sberbank.ru.
  • Документация nginx: модуль ngx_http_headers_module (add_header и наследование): nginx.org; модуль ngx_http_ssl_module: nginx.org.
  • OWASP Secure Headers Project: owasp.org. Международная практика, не российский норматив.

Оставьте свой ответ