Security headers: HSTS, CSP, X-Frame-Options и другие
· 7 мин чтения
Бэкенд - крепость, но вторая половина защиты в браузере. HSTS, CSP и другие заголовки блокируют XSS, clickjacking и MITM - и это бесплатно.
Почему они важны
Приложение может быть идеально защищено на стороне backend и всё равно оставаться уязвимым к атакам на стороне браузера: cross-site scripting (XSS), clickjacking, protocol downgrade, MIME confusion. Security-заголовки переносят защиту в сам браузер.
Strict-Transport-Security (HSTS)
Принудительно включает HTTPS для всех будущих посещений. После первого визита по HTTPS браузер запоминает домен и сам переписывает любую http://-ссылку на https://, даже если пользователь кликнет по плохой ссылке.
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
max-age=63072000= действует 2 годаincludeSubDomains= правило действует и для всех поддоменов; оно принудительно включает HTTPS везде, поэтому поддомен, работающий только по HTTP, напр.staging.example.com, станет недоступным в браузереpreload= позволяет включение в список HSTS preload, встроенный в Chrome / Firefox / Safari
Внимание:
Удаление HSTS с preload из списка занимает 6+ месяцев. Не добавляйте preload, пока не убедитесь, что HTTPS стабильно работает на всех поддоменах.
Content-Security-Policy (CSP)
Самый мощный, но и самый сложный в настройке заголовок. Он задаёт whitelist того, откуда браузеру разрешено загружать скрипты, стили, изображения и iframe-ы. Без CSP атакующий, способный инжектировать <script> тег, может выполнить любой JS; с CSP - только код из одобренных источников.
Content-Security-Policy: default-src 'self'; script-src 'self' 'sha256-...'; img-src 'self' data: https://cdn.example.com; style-src 'self' 'unsafe-inline'; frame-ancestors 'none'
Распространённые директивы:
default-src 'self'= по умолчанию разрешать только ресурсы со своего доменаscript-src= JS-источники (для inline-скриптов укажите hash или nonce)style-src= CSS-источникиimg-src= изображенияconnect-src= AJAX, WebSocket, EventSourceframe-ancestors 'none'= никто не может встроить страницу в iframe (лучше, чем X-Frame-Options)report-uri /csp-report= браузер отправляет JSON при каждом нарушении (вы ловите его в backend и логируете)- Примечание:
report-uriсегодня deprecated - браузеры с поддержкойreport-toего игнорируют. Современный способ - директиваreport-toплюс отдельный заголовокReporting-Endpoints; оставьтеreport-uriтолько как fallback для Firefox, который пока не поддерживаетreport-to.
X-Frame-Options
Более старая альтернатива frame-ancestors. Защищает от clickjacking, когда атакующий встраивает вашу страницу как iframe и перекрывает её невидимыми кнопками.
X-Frame-Options: DENY
Значения: DENY (никто), SAMEORIGIN (только мой домен), ALLOW-FROM uri (deprecated).
X-Content-Type-Options
X-Content-Type-Options: nosniff
Отключает MIME sniffing, поэтому браузер уважает Content-Type, возвращённый сервером. Без этого атакующий может загрузить файл с неправильным типом (например, изображение, которое на самом деле HTML со скриптом), и браузер может выполнить его как веб-страницу.
Referrer-Policy
Referrer-Policy: strict-origin-when-cross-origin
Управляет тем, сколько информации о предыдущей странице браузер отправляет в заголовке Referer при клике. Значение по умолчанию в современных браузерах уже strict-origin-when-cross-origin, но явная установка заголовка гарантирует согласованное поведение.
Permissions-Policy
Отключает API, которые вам не нужны, такие как камера, микрофон, GPS и геолокация. Атакующий, использующий XSS, не может запросить эти API.
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Практическая конфигурация в nginx
server {
listen 443 ssl http2;
server_name example.com;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
# preload здесь намеренно опущен - добавляй его только когда уверен на 100% (см. ловушку с preload выше)
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# CSP обширна - определяйте её согласно вашему приложению
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; ..." always;
}
Ключевое - always: без него nginx пропускает заголовки при error response (4xx, 5xx).
Аудит текущего состояния
Проще всего через онлайн-инструмент. Наш header check покажет вам, какие заголовки отсутствуют и какие неправильны. Для тщательного аудита securityheaders.com дополнительно даёт оценку A-F.
Вывод
Security-заголовки - одна из лучших инвестиций в безопасность по соотношению усилий и выгоды: обычно полчаса настройки в reverse proxy против целой категории атак на стороне браузера. Аудит раз в полгода в рамках регулярного обслуживания - разумный минимум.
Бесплатный аудит security headers
Без регистрации, результат за три секунды.
Попробуйте ePulz.io бесплатно - 7 дней без банковской карты.
Создать аккаунт