Безопасность сайта и заголовки 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, браузеры пользователей откажутся открывать сайт, и обойти это они не смогут. Поэтому я обычно действую в таком порядке:
- Убеждаюсь, что весь сайт и все поддомены, которые нужны, открываются по HTTPS, а редирект с http на https работает одним шагом.
- Включаю HSTS с маленьким сроком, например на сутки, и смотрю, что ничего не сломалось.
- Увеличиваю срок до недель, затем до года.
- Параметр
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 ничего не блокирует, а только фиксирует нарушения. Вы выкладываете политику в таком режиме, неделю ходите по сайту и смотрите консоль браузера, пробуете формы, оплату, карты, чат. Все срабатывания показывают, что вы забыли разрешить.
Порядок действий:
- Составьте инвентарь внешних ресурсов: посмотрите вкладку Network и исходный код страниц.
- Выложите политику в режиме Report-Only.
- Пройдите все ключевые сценарии, включая оплату и отправку заявок, на настольном компьютере и телефоне.
- Устраните нарушения: либо разрешите нужный источник, либо уберите ненужный скрипт, которого вы не ждали.
- Переключите заголовок в боевой режим и ещё какое-то время смотрите на ошибки.
Если у вас подключены платёжные страницы банков или эквайринг, обязательно проверьте именно этот сценарий. Банковские формы часто открываются во фрейме или через редирект, подгружают собственные скрипты, и точные адреса, которые надо разрешить, нужно брать из актуальной документации провайдера. Заранее угадывать их не стоит.
Какие ещё заголовки стоит добавить
Кроме 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.