· 8 мин чтения

Безопасность сайта и заголовки CSP, HSTS простыми словами

Безопасность сайта на уровне заголовков сводится к нескольким строкам в ответе сервера, которые говорят браузеру, что можно делать со страницей, а что нельзя. HSTS заставляет использовать только HTTPS, CSP ограничивает источники скриптов и стилей, а ещё три-четыре простых заголовка закрывают встраивание в чужие фреймы, угадывание типов файлов и утечку адресов страниц. Включить их можно за пару часов, но делать это нужно по порядку и с проверкой, иначе легко сломать форму оплаты или виджет чата.

Зачем сайту компании защитные заголовки

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

Для сайта услуг или интернет-магазина типичные риски такие: подмена или перехват трафика на публичном Wi-Fi, внедрение чужого скрипта через взломанный сторонний виджет, встраивание вашей страницы в невидимый фрейм на чужом сайте (кликджекинг), утечка полного адреса страницы со служебными параметрами на внешний ресурс. Против каждого есть свой заголовок.

Проверить, что у вас сейчас, можно за минуту: откройте инструменты разработчика в браузере, вкладку Network, выберите главный документ и посмотрите Response Headers. Либо выполните в терминале:

curl -sI https://example.kz/ | sort

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

Как работает HSTS и когда его включать

HSTS (HTTP Strict Transport Security) сообщает браузеру: «этот домен открывай только по HTTPS на указанный срок». После первого визита браузер сам заменит http:// на https:// ещё до отправки запроса. Это закрывает атаку, при которой злоумышленник в вашей сети перехватывает первый незащищённый запрос и подсовывает поддельную страницу.

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

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

Тут есть ловушка. Если вы включили HSTS с большим сроком, а потом у сертификата закончилась действующая дата или один из поддоменов оказался без HTTPS, браузеры пользователей откажутся открывать сайт, и обойти это они не смогут. Поэтому я обычно действую в таком порядке:

  1. Убеждаюсь, что весь сайт и все поддомены, которые нужны, открываются по HTTPS, а редирект с http на https работает одним шагом.
  2. Включаю HSTS с маленьким сроком, например на сутки, и смотрю, что ничего не сломалось.
  3. Увеличиваю срок до недель, затем до года.
  4. Параметр includeSubDomains добавляю, только когда проверил каждый поддомен, включая забытые тестовые и почтовые панели.

Параметр preload и внесение домена в список предзагрузки браузеров стоит рассматривать отдельно: откатить его сложно и долго. Для небольшого бизнес-сайта обычно достаточно обычного HSTS. Связанную тему про домен, сертификат и почту я разбирал в статье про DNS, SSL и записи SPF, DKIM, DMARC.

Content-Security-Policy, главный и самый капризный заголовок

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

Минимальный пример для простого сайта, где всё своё, а аналитика и карта подключены с внешних адресов:

Content-Security-Policy: default-src 'self'; script-src 'self' https://www.googletagmanager.com; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; frame-ancestors 'self'

Разберём по частям. default-src 'self' значит «по умолчанию только с моего домена». script-src перечисляет разрешённые источники скриптов. img-src разрешает картинки со своего домена, из data-URI и с любых HTTPS-адресов. frame-ancestors определяет, кто вправе встраивать вашу страницу.

Главная трудность в том, что современные сайты тянут много чужого: счётчики, чаты, карты, видео, шрифты, виджеты оплаты. Каждый источник надо внести в политику. Есть и вторая проблема: встроенные в страницу скрипты (inline) CSP блокирует по умолчанию. Для них используют nonce, одноразовое значение, которое сервер генерирует на каждый запрос и подставляет и в заголовок, и в тег <script nonce="...">. Значение 'unsafe-inline' для скриптов снижает пользу политики почти до нуля, поэтому я оставляю его только для стилей, да и то если иначе никак.

Как внедрить CSP и не сломать сайт

Правильный путь начинается с режима наблюдения. Заголовок Content-Security-Policy-Report-Only ничего не блокирует, а только фиксирует нарушения. Вы выкладываете политику в таком режиме, неделю ходите по сайту и смотрите консоль браузера, пробуете формы, оплату, карты, чат. Все срабатывания показывают, что вы забыли разрешить.

Порядок действий:

  1. Составьте инвентарь внешних ресурсов: посмотрите вкладку Network и исходный код страниц.
  2. Выложите политику в режиме Report-Only.
  3. Пройдите все ключевые сценарии, включая оплату и отправку заявок, на настольном компьютере и телефоне.
  4. Устраните нарушения: либо разрешите нужный источник, либо уберите ненужный скрипт, которого вы не ждали.
  5. Переключите заголовок в боевой режим и ещё какое-то время смотрите на ошибки.

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

Какие ещё заголовки стоит добавить

Кроме HSTS и CSP есть несколько простых заголовков, которые почти не бывают причиной проблем.

  • X-Content-Type-Options: nosniff запрещает браузеру «догадываться» о типе файла. Без него загруженный пользователем файл с неправильным расширением может быть исполнен как скрипт.
  • Referrer-Policy: strict-origin-when-cross-origin не передаёт на внешние сайты полный адрес вашей страницы с параметрами, только домен.
  • X-Frame-Options: SAMEORIGIN запрещает встраивать страницы в чужие фреймы. Современный аналог живёт в CSP как frame-ancestors, но старый заголовок полезен для совместимости.
  • Permissions-Policy отключает ненужные возможности браузера: камеру, микрофон, геолокацию. Если сайту они не нужны, нет причин их оставлять доступными для встроенных виджетов.
  • Cross-Origin-Opener-Policy и родственные заголовки нужны реже и могут ломать всплывающие окна авторизации, поэтому включайте их осознанно.

Не забывайте и про cookie: сессионные cookie должны иметь флаги Secure, HttpOnly и разумное значение SameSite. Это не заголовок страницы, но тоже часть той же гигиены.

Где и как прописать заголовки

Заголовки можно задавать на трёх уровнях: в конфигурации веб-сервера, в коде приложения или в панели CDN. Для nginx это выглядит так:

add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000" always;

Слово always важно: без него nginx не добавит заголовок к ответам с кодами ошибок. На обычном виртуальном хостинге с Apache то же делается через .htaccess и директиву Header set. Если сайт написан на PHP без фреймворка, заголовки можно выставлять функцией header() в общем подключаемом файле, но для статических файлов лучше конфигурация сервера.

Один частый промах: заголовки задали и в приложении, и на сервере, и в CDN, а значения разошлись. Браузер получит дубли, а для CSP это значит, что действуют обе политики сразу, то есть применяется пересечение ограничений. Выберите одно место и держите всё там. Про хостинг и его возможности в этом плане есть отдельный разбор: как выбрать хостинг для сайта бизнеса.

Как понять, что заголовки работают, и не забыть про них потом

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

Полезно завести короткий документ на одну страницу: какие заголовки включены, какие значения, где они задаются и кто за них отвечает. Через год, когда кто-то подключит новый виджет и он перестанет работать, именно такой документ сэкономит время. Вся логика проста: любое новое внешнее подключение должно сопровождаться вопросом «нужно ли добавить его адрес в CSP».

Если у сайта есть отчёты о нарушениях политики (директива report-uri или report-to), следите за ними хотя бы первое время. Они покажут, что именно блокируется у реальных посетителей, в том числе из-за расширений браузера, которые к вам отношения не имеют. Этот шум придётся отфильтровать, но полезные сигналы там встречаются.

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

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

Достаточно ли заголовков, чтобы сайт считался защищённым?

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

Можно ли включить CSP на сайте с готовой CMS и десятком плагинов?

Можно, но придётся потратить время. Плагины часто вставляют inline-скрипты и тянут ресурсы с разных адресов. Начинайте с Report-Only, а если политика получается слишком дырявой, начните с более простых заголовков и постепенно сокращайте список плагинов.

Влияют ли заголовки безопасности на SEO?

Напрямую это не фактор ранжирования, который можно обещать. Косвенно HTTPS и отсутствие взломов важны: заражённый сайт теряет позиции и доверие. А вот неправильная CSP может заблокировать скрипт, от которого зависит отображение страницы, и это уже заметят и посетители, и поисковые роботы.

Как проверить результат?

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

Если хотите, чтобы заголовки, HTTPS и прочие базовые меры были настроены на вашем сайте без сюрпризов, посмотрите страницу о создании сайтов или напишите мне в Telegram.