Webrium
Подписаться

Заголовки безопасности: какие ставить и что они ломают

Что ломается при неправильной настройке заголовков безопасности и как этого избежать

Схема разделенного поля: слева заголовки безопасности добавлены, справа сайт не работает из-за неправильной настройкиЗаголовки добавленыСайт сломался

Заголовки безопасности: что они делают и почему не работают «из коробки»

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

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

Тупик: почему просто добавить заголовки недостаточно

Первое, что обычно пробуют — взять список рекомендованных заголовков и добавить их на сайт. Кажется логичным: если эти заголовки защищают, то чем больше их добавить, тем лучше. Но на практике это приводит к проблемам:

  1. Сайт ломается. Например, если добавить CSP в режиме запрета без предварительной настройки, перестанут работать встроенные скрипты, виджеты, аналитика. Пользователи увидят пустую страницу или неработающие элементы интерфейса.

  2. Заголовки конфликтуют друг с другом. Например, X-Frame-Options: DENY и CSP с frame-ancestors 'self' говорят разное, и результат будет зависеть от браузера. Первый запрещает встраивание страницы вообще, второй разрешает его на своём домене. Браузер, понимающий frame-ancestors, применит её и проигнорирует X-Frame-Options, а старый браузер — наоборот, и страница поведёт себя в них по-разному.

  3. Заголовки не работают из-за неверных настроек. Например, со сроком max-age у HSTS ошибаются в обе стороны. Слишком короткий срок оставляет окно: когда он истёк, первый запрос снова уходит по HTTP и может быть перехвачен. Слишком длинный запирает сайт на HTTPS: если сертификат сломается или понадобится вернуть HTTP, браузеры посетителей будут требовать HTTPS до конца срока, а со стороны сайта снять правило можно только ответом max-age=0 по работающему HTTPS.

  4. Заголовки не защищают от реальных атак. Например, CSP не устраняет саму дыру. Если данные попадают в разметку без экранирования, строгая политика мешает выполнить внедрённый скрипт, но не мешает внедрить разметку. А политика, которая разрешает встроенные скрипты ('unsafe-inline' без nonce или хеша), от внедрённого скрипта не защищает.

Поэтому просто добавить заголовки недостаточно. Их нужно настраивать под конкретный сайт, проверять и исправлять нарушения.

Какие заголовки действительно работают

Content-Security-Policy (CSP)

CSP — это политика содержимого, которая ограничивает источники загрузки ресурсов: скриптов, стилей, изображений, шрифтов. Она нужна, чтобы ограничить последствия межсайтового скриптинга (XSS): браузер выполняет только скрипты из разрешённых источников и не выполняет встроенный в разметку скрипт или обработчик события, если они явно не разрешены. Поэтому встроенный скрипт или обработчик события, внедрённый в страницу через ошибку, не запустится, пока политика сама не разрешает встроенные скрипты. А вот внедрённый тег, который подключает скрипт с разрешённого источника, выполнится. Если на разрешённом домене лежит код, пригодный атакующему, — общий CDN с библиотеками, файлы, загруженные пользователями, — политика со списком доменов его не остановит. Надёжнее разрешать скрипты по nonce или хешу, а не по списку доменов.

Но CSP — это не просто заголовок, который можно включить и забыть. Если включить его сразу в режиме запрета, сайт сломается. Поэтому CSP внедряют в три этапа:

  1. Режим отчёта (Content-Security-Policy-Report-Only). Браузер не блокирует ресурсы, но отправляет отчёты о нарушениях на указанный URL. Это позволяет понять, какие ресурсы нарушают политику, и исправить их до включения запрета.
  2. Разбор нарушений. На этом этапе нужно проанализировать отчёты и исправить проблемы: заменить встроенные скрипты на внешние, добавить разрешения для сторонних доменов, обновить библиотеки.
  3. Режим запрета (Content-Security-Policy). Только после того, как все нарушения устранены, можно включить CSP в режиме запрета.

Пример CSP в режиме отчёта:

Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' https://cdn.example.com; report-uri /csp-report-endpoint

Что ломается при включении CSP:

  • Встроенные скрипты. Их нужно вынести в отдельные файлы.
  • Сторонние виджеты. Для них нужно добавить разрешение на загрузку с их домена.
  • Аналитика. Для неё тоже нужно добавить разрешение на загрузку с домена аналитического сервиса.
  • Стили и шрифты, загружаемые с CDN. Их нужно либо разместить на своём домене, либо добавить разрешение на загрузку с CDN.

Strict-Transport-Security (HSTS)

HSTS — это заголовок, который принудительно переводит все соединения на HTTPS. Он нужен, чтобы предотвратить атаки через понижение протокола (downgrade attacks): если злоумышленник сможет перехватить соединение и заставить браузер использовать HTTP, он сможет украсть куки или внедрить вредоносный код.

HSTS работает так: когда браузер получает этот заголовок, он запоминает, что сайт должен открываться только по HTTPS, и автоматически переводит все HTTP-запросы на HTTPS. Заголовок выглядит так:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Что означают параметры:

  • max-age — время, в течение которого браузер будет помнить о необходимости использовать HTTPS. Срок отсчитывается заново при каждом ответе с заголовком по HTTPS и со сроком сертификата не связан. Со слишком коротким сроком после его истечения первый запрос снова уходит по HTTP и может быть перехвачен. Со слишком длинным браузеры посетителей будут требовать HTTPS до конца срока, а снять правило можно только ответом max-age=0 по работающему HTTPS.
  • includeSubDomains — распространяет правило на все поддомены.
  • preload — сам по себе сайт никуда не вносит: это согласие владельца на включение домена в предзагруженный список HSTS. В список сайт попадает только после отдельной заявки и выхода версий браузеров, в которые список вшит. После этого браузер знает о необходимости HTTPS ещё до первого посещения. Чтобы заявку приняли, домен должен соответствовать требованиям: достаточно длинный max-age, наличие includeSubDomains, сама директива preload, работающий HTTPS на всех поддоменах и перенаправление с HTTP на HTTPS.

Что ломается при включении HSTS:

  • Если на сайте есть страницы, которые должны открываться по HTTP, они перестанут работать.
  • Если сертификат SSL недействителен, браузер не сможет открыть сайт вообще.
  • Если сайт добавлен в предзагруженный список HSTS, его нельзя будет удалить оттуда без обновления всех браузеров.

X-Frame-Options

Этот заголовок запрещает встраивать страницу в рамку (<iframe>). Он нужен, чтобы предотвратить кликджекинг: атаку, при которой злоумышленник встраивает вашу страницу в свою и обманом заставляет пользователя нажать на кнопку.

Заголовок выглядит так:

X-Frame-Options: DENY

Или так:

X-Frame-Options: SAMEORIGIN

Что означают параметры:

  • DENY — запрещает встраивание страницы в рамку вообще.
  • SAMEORIGIN — разрешает встраивание только на страницах того же источника.

Что ломается при включении X-Frame-Options:

  • Если на сайте есть страницы, которые должны встраиваться в рамки, они перестанут работать.
  • Если сайт использует сторонние сервисы, которые встраивают его в рамки, они тоже перестанут работать.

X-Content-Type-Options

Этот заголовок с единственным значением nosniff запрещает браузеру угадывать тип содержимого вопреки объявленному сервером. Браузеры его соблюдают: скрипт или стиль, отданный с неподходящим Content-Type, при nosniff не выполняется и не применяется. Без заголовка браузер может выполнить как скрипт файл, который сервер отдал как текст, а файл без указанного типа — показать как страницу. Так и срабатывает атака через загруженный пользователем файл. Заголовок не устарел, и ломает он только ресурсы, отданные с неверным типом: их нужно отдавать с правильным Content-Type.

Referrer-Policy

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

Заголовок выглядит так:

Referrer-Policy: strict-origin-when-cross-origin

Что означают параметры:

  • no-referrer — не отправлять заголовок Referer вообще.
  • no-referrer-when-downgrade — не отправлять Referer при переходе с HTTPS на HTTP.
  • same-origin — отправлять Referer только при переходах внутри одного источника.
  • strict-origin — отправлять только источник (схему, хост и порт) при любых переходах, в том числе к тому же источнику, и ничего не отправлять при переходе с HTTPS на HTTP.
  • strict-origin-when-cross-origin — отправлять полный URL при переходах внутри одного источника, только источник при переходах на другие источники и ничего не отправлять при переходе с HTTPS на HTTP.

Что ломается при включении Referrer-Policy:

  • Если на сайте есть аналитика, которая использует заголовок Referer для отслеживания переходов, она может перестать работать.
  • Если сайт использует сторонние сервисы, которые полагаются на Referer, они тоже могут перестать работать.

Какие заголовки ничего не делают

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

X-XSS-Protection

Этот заголовок должен был включать встроенную защиту от XSS в браузерах, но современные браузеры игнорируют его. Вместо него используют CSP.

Expect-CT

Этот заголовок должен был проверять сертификаты на соответствие Certificate Transparency, но современные браузеры делают это автоматически. Заголовок устарел и не влияет на поведение браузера.

Порядок внедрения заголовков безопасности

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

  1. Включить режим отчёта для CSP. Это позволит понять, какие ресурсы нарушают политику, и исправить их до включения запрета.
  2. Проанализировать отчёты CSP. На этом этапе нужно исправить все нарушения: заменить встроенные скрипты на внешние, добавить разрешения для сторонних доменов, обновить библиотеки.
  3. Включить CSP в режиме запрета.
  4. Включить HSTS. Начать с небольшого max-age, чтобы проверить, что правило работает корректно, и постепенно увеличивать его.
  5. Включить X-Frame-Options. Начать с SAMEORIGIN, чтобы не сломать виджеты на своём домене.
  6. Включить X-Content-Type-Options. Установить значение nosniff, чтобы браузер не угадывал тип содержимого.
  7. Включить Referrer-Policy. Начать с strict-origin-when-cross-origin, чтобы не сломать аналитику.

Как проверить, что заголовки действительно отдаются

Заголовки безопасности должны отдаваться на всех страницах сайта, а не только на главной. Чтобы проверить это, можно использовать инструменты разработчика в браузере:

  1. Открыть инструменты разработчика.
  2. Перейти на вкладку «Network» (Сеть).
  3. Обновить страницу.
  4. Найти запрос к странице и проверить заголовки ответа.

Или использовать командную строку:

curl -I https://example.com/path/to/page

Что проверять:

  • Заголовки должны отдаваться на всех страницах, а не только на главной. Для этого нужно проверить несколько страниц с разными путями.
  • Заголовки должны быть корректно настроены. Например, CSP не должен блокировать нужные ресурсы, а HSTS должен иметь адекватное значение max-age.
  • Заголовки не должны конфликтовать друг с другом.

Скопировать чужой набор заголовков целиком — плохая идея

Часто встречается совет: «Возьмите набор заголовков безопасности с чужого сайта и поставьте у себя». Это плохая идея, потому что:

  1. Чужой набор может не подходить вашему сайту. Например, если на чужом сайте нет встроенных скриптов, а на вашем есть, CSP сломает их.
  2. Чужой набор может быть устаревшим. Например, если на чужом сайте стоит X-XSS-Protection, а современные браузеры его игнорируют.
  3. Чужой набор может конфликтовать с вашими сервисами. Например, если на чужом сайте стоит Referrer-Policy: no-referrer, а ваша аналитика использует Referer, она перестанет работать.
  4. Чужой набор может быть избыточным.

Вместо этого нужно настраивать заголовки безопасности под свой сайт, постепенно и с проверкой.

Позиция редакции

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

Заголовки безопасности эффективны только при соблюдении следующих условий:

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

Позиция редакции неверна, если:

  • На сайте нет реальных угроз, которые можно предотвратить с помощью заголовков безопасности. Например, если сайт статический и не содержит пользовательского ввода, XSS и кликджекинг ему не страшны.
  • Заголовки безопасности мешают работе сайта больше, чем защищают его. Например, если CSP блокирует критически важные ресурсы, а исправить это невозможно, лучше отключить CSP, чем оставить сайт неработающим.
  • На сайте уже реализованы более эффективные меры защиты. Например, если все страницы собираются шаблонизатором, который экранирует любой вывод, а сторонних скриптов на сайте нет, строгая CSP остаётся страховкой на случай ошибки, а не основной защитой, и её внедрение можно отложить.

С чем это связано

Метка над заголовком говорит, зачем туда идти: продолжить тему, перейти к практике или увидеть возражение.

На шкале времени жизни экрана величина — момент, когда становятся известны входные данные, — пробивает пороговую линию между сервером и клиентом: если данные изсервер/клиентвремя жизни экрана
Практика

Где проходит граница клиент — сервер

Как провести границу клиент-сервер, чтобы не сломать архитектуру при изменении входных данных.

Две угрозы по разные стороны: одна закрывается хранилищем, другая — серверомчужой скриптчужой сайт
Усиление

Токен в браузере: где его хранить

Как правильно хранить токен в браузере — и почему выбор места зависит не от удобства, а от модели угроз.

Ответ из кеша совпадает с источником или разошёлся с нимсовпалоустарело
Углубление

Кеширование по HTTP: заголовки, которые действительно работают

Как правильно кешировать по HTTP: разбираем заголовки, которые управляют сроком годности и проверкой контента — и почему их путают чаще всего.

Письмо по вторникам

Один разбор недели и короткий список того, что изменилось. Без дайджестов на сорок ссылок.

Начните вводить — материалы появятся здесь.

↑↓ выбрать · Enter открыть · Esc закрыть