Что делают заголовки безопасности и чего не делают
Заголовок безопасности — это заголовок ответа, который просит браузер обращаться с вашей страницей строже, чем по умолчанию: работать только по HTTPS, не угадывать типы файлов, не встраиваться в фреймы чужих сайтов, меньше сообщать в Referer, не включать камеру и выполнять только одобренные вами скрипты.
Это эшелонированная защита. Ни один из них не исправит уязвимое приложение: SQL-инъекция или сломанный вход остаются такими же сломанными при идеальном наборе заголовков. Их задача — ограничить ущерб от ошибок, которые браузер способен сдержать: cross-site scripting, clickjacking, понижения протокола, утечки данных через referrer. Делают они это для каждого посетителя ценой нескольких сотен байт.
И работают они только в браузерах. API-клиент, краулер или скрипт атакующего игнорирует их все, поэтому первой линией остаются WAF и сам код приложения.
Strict-Transport-Security (HSTS)
Strict-Transport-Security велит браузеру использовать для вашего хоста HTTPS и помнить это max-age секунд, так что даже набранный вручную адрес http:// не покидает устройство открытым текстом. Это заголовок с наибольшим эффектом при наименьших усилиях — при условии, что каждый URL сайта уже работает по HTTPS.
Безопасное начало — max-age=31536000: один год, только этот хост. includeSubDomains и preload действуют шире, и отменить их трудно. В руководстве по HSTS объяснено, к чему обязывает каждый из них, как проверить заголовок и как откатиться.
На CDN.com.tr HSTS — это пресет аккаунта в Правилах доставки, так что писать для него строку заголовка не нужно. Он отправляет ровно max-age=31536000 в ответах по HTTPS и ничего по обычному HTTP.
X-Content-Type-Options: nosniff
Раньше браузеры угадывали тип файла по его содержимому, если заголовок Content-Type казался неверным. Это угадывание, MIME sniffing, позволяло загруженному файлу, похожему на скрипт, выполниться как скрипт. X-Content-Type-Options: nosniff его отключает: таблица стилей должна прийти как text/css, а скрипт — с JavaScript-типом, иначе браузер их отвергнет.
Единственное значение — nosniff, и заголовок безопасен в любом ответе, HTML или нет. Проверьте заранее одно: правильно ли сервер помечает файлы. Скрипт, отданный как text/plain, перестанет загружаться в тот же момент, как появится заголовок, — и это ровно то поведение, о котором вы просите.
X-Frame-Options и frame-ancestors: кто может встраивать ваши страницы
Clickjacking загружает вашу страницу в невидимый фрейм на чужом сайте и совмещает одну из его кнопок с вашей: посетитель нажимает «Удалить аккаунт», думая, что нажал «Воспроизвести». Защита — сказать браузеру, кому разрешено помещать вашу страницу во фрейм.
У X-Frame-Options два полезных значения: DENY — никому, SAMEORIGIN — страницам вашего же источника. Старое значение ALLOW-FROM современные браузеры игнорируют. Чтобы разрешить список сайтов, используйте директиву CSP frame-ancestors, например frame-ancestors 'self' https://partner.example.
Если в ответе есть оба, браузеры, понимающие frame-ancestors, следуют ему и игнорируют X-Frame-Options. Отправлять оба — обычная и безвредная практика, старый заголовок прикрывает старые браузеры, лишь бы оба говорили одно и то же. Одно ограничение: frame-ancestors работает только как HTTP-заголовок, в теге <meta> — никогда.
Referrer-Policy: что уходит с каждым кликом
Когда посетитель переходит по ссылке или ваша страница загружает картинку, скрипт или шрифт с другого сайта, браузер может отправить адрес вашей страницы в заголовке Referer. Полные URL выдают больше, чем кажется: поисковые запросы, номера заказов, токен из ссылки для сброса пароля.
Разумное значение — strict-origin-when-cross-origin: полный URL внутри вашего сайта, только источник (https://example.com/) для чужих сайтов и ничего при переходе со страницы HTTPS на обычный HTTP. Современные браузеры и так применяют это или что-то строже, если страница ничего не указала; явная настройка закрепляет поведение, каким бы ни стало значение по умолчанию в браузере. Для приложения с чувствительными URL no-referrer не отправляет ничего — ценой данных о переходах в аналитике других сайтов.
Permissions-Policy: отключите то, чем не пользуетесь
Permissions-Policy определяет, какими мощными функциями браузера могут пользоваться ваша страница и фреймы внутри неё: камерой, микрофоном, геолокацией, платежами, USB и другими. Пустой список () выключает функцию для всех, (self) разрешает её только вашему источнику, а ещё можно перечислить источники встраиваемого контента, которому она действительно нужна.
Корпоративному сайту или магазину почти ничего из этого не требуется, поэтому camera=(), microphone=(), geolocation=() — хорошее начало; добавляйте остальное по мере ревизии того, что вы встраиваете. Выигрыш — в изоляции: взломанный сторонний скрипт или рекламный фрейм не сможет запросить у посетителя камеру. Заголовок применяют браузеры на базе Chromium, поэтому считайте его дополнительным усилением поверх остальных мер, а не их заменой.
Content-Security-Policy: сильная защита, требующая плана
Content Security Policy сообщает браузеру, откуда могут загружаться скрипты, стили, картинки, шрифты, соединения и фреймы и можно ли выполнять встроенный код. Хорошо настроенная политика превращает большинство ошибок cross-site scripting из «атакующий выполняет код в сессиях ваших пользователей» в «браузер заблокировал скрипт и сообщил об этом».
Это же и заголовок, который ломает страницы. Встроенные блоки <script>, атрибуты onclick, менеджер тегов, подгружающий новые скрипты, забытый адрес аналитики — каждый придётся разрешить или переписать. Поэтому внедряйте её в два шага. Сначала отправляйте политику как Content-Security-Policy-Report-Only: ничего не блокируется, а каждое нарушение видно в консоли и, если задан адрес report-to или report-uri, в ваших отчётах. Исправьте или разрешите то, что покажут отчёты, и затем отправляйте ту же политику как Content-Security-Policy.
Политика ниже строгая, но читаемая. Для встроенных скриптов, которые убрать нельзя, генерируйте случайный nonce на каждый ответ и указывайте его и в политике, и в теге <script>. С 'strict-dynamic' скрипты, загруженные доверенным скриптом, тоже считаются доверенными, и менеджеры тегов работают без перечисления всех доменов, к которым обращаются. Избегайте 'unsafe-inline' для скриптов: он отключает большую часть защиты, ради которой политика существует.
Строгая стартовая политика в режиме report-only
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests
Безопасные значения для копирования
Большинство сайтов может начать с этого набора и поправить только источники CSP и список разрешений. Порядок не важен; важно, чтобы каждый заголовок встречался один раз.
X-XSS-Protection здесь нет намеренно. Он управлял фильтром, который из современных браузеров убран, а в старых сам фильтр можно было использовать во вред; не отправляйте ничего или отправляйте X-XSS-Protection: 0. Expect-CT устарел по сходной причине, его тоже можно убрать.
Заголовки ответа для HTML-страницы
Strict-Transport-Security: max-age=31536000
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Content-Security-Policy-Report-Only: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'
Настройка на CDN.com.tr
У HSTS свой пресет, о нём выше. Остальные заголовки с одним значением задаются в правиле доставки: в Правилах доставки отредактируйте правило, которое отдаёт ваши страницы, обычно /, и добавьте их по одному в строке в поле Пользовательские заголовки в виде Имя-Заголовка значение (на странице правил с вкладками: Заголовки → Добавить заголовки ответа). Значение с пробелами берётся в двойные кавычки.
Полная Content-Security-Policy туда не помещается. Её директивы разделяются точкой с запятой, а панель и edge не принимают ; в строке заголовка, потому что точка с запятой завершила бы собственную директиву конфигурации edge. В одной директиве точки с запятой нет, поэтому одиночный frame-ancestors на edge работает. Полной политике место на origin-сервере — в веб-сервере или приложении, где она к тому же может нести nonce для каждого запроса. Подойдёт и HTML-тег <meta http-equiv="Content-Security-Policy">, кроме frame-ancestors, report-uri и sandbox, которые браузеры в meta-теге игнорируют.
Заголовки, добавленные правилом, применяются к успешным ответам и перенаправлениям (2xx и 3xx), в том числе отданным из кэша, сразу после публикации правила; заголовок, который должен быть и на страницах ошибок, ставится на origin-сервере. Заголовки, которые отправляет origin-сервер, хранятся вместе с каждой копией в кэше, поэтому после их изменения выполните очистку кэша CDN для затронутых страниц. Если origin-сервер уже отправляет какой-то из этих заголовков, выберите его в списке Скрыть Заголовки того же правила, иначе посетители получат две копии.
Пользовательские заголовки в правиле, которое отдаёт страницы
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
Referrer-Policy strict-origin-when-cross-origin
Permissions-Policy "camera=(), microphone=(), geolocation=()"
Content-Security-Policy "frame-ancestors 'self'"
Как проверить
Начните с curl: он показывает ровно то, что отправляет сервер. Проверьте страницу, статический файл и несуществующую страницу — заголовки, заданные в одном месте, часто отсутствуют в двух других.
Затем откройте инструменты разработчика в браузере. Вкладка Network показывает заголовки в том виде, в каком их получил браузер, а консоль сообщает о каждом нарушении CSP и каждом отклонённом фрейме с указанием директивы. Онлайн-сканеры вроде HTTP Observatory от Mozilla оценивают весь набор и объясняют каждую находку — полезно как второе мнение.
Наконец, проверяйте поведение, а не только наличие: встройте страницу в iframe на другом источнике и убедитесь, что браузер это отклоняет, а перед включением понаблюдайте за консолью несколько дней с политикой report-only.
curl -sI https://www.example.com/ | grep -i -E \
'strict-transport|x-content-type|x-frame|referrer-policy|permissions-policy|content-security'
# a static file and a missing page, too
curl -sI https://www.example.com/assets/app.css | grep -i x-content-type
curl -sI https://www.example.com/no-such-page | grep -i -E 'x-frame|content-security'
Частые вопросы о заголовках безопасности
Какие заголовки безопасности должен отправлять каждый сайт?
HSTS, когда весь сайт работает по HTTPS; X-Content-Type-Options: nosniff; X-Frame-Options: SAMEORIGIN или frame-ancestors в CSP; Referrer-Policy: strict-origin-when-cross-origin; и Permissions-Policy, отключающий неиспользуемые функции. Content-Security-Policy добавляйте после пробного периода в режиме report-only.
X-Frame-Options устарел?
Скорее превзойдён, чем отменён. frame-ancestors из CSP делает то же самое гибче, и браузеры, которые его поддерживают, игнорируют X-Frame-Options, если пришли оба. Отправка SAMEORIGIN или DENY рядом с ним по-прежнему защищает старые браузеры; мёртв только ALLOW-FROM.
Нужно ли ещё отправлять X-XSS-Protection?
Нет. Фильтр, которым он управлял, удалён из современных браузеров, а в старых его можно было обратить против страницы. Не отправляйте его или отправляйте 0 и полагайтесь на Content-Security-Policy.
Можно ли задать Content-Security-Policy через CDN.com.tr?
Одну директиву — да: например, frame-ancestors 'self' в Пользовательских заголовках правила доставки. Полной политике нужны точки с запятой между директивами, а правила edge их не принимают, поэтому задавайте её в веб-сервере или приложении либо в meta-теге — для всего, кроме frame-ancestors, report-uri и sandbox.
Влияют ли заголовки безопасности на SEO или скорость?
Заметно — нет. Они добавляют несколько сотен байт, а HTTP/2 и HTTP/3 сжимают повторяющиеся заголовки. Документированным фактором ранжирования они не являются. Единственный риск для SEO — CSP, которая блокирует ваши же скрипты и ломает отрисовку страницы, а этап report-only это выявляет.
Нужны ли эти заголовки картинкам, CSS и JavaScript?
nosniff и HSTS полезны в любом ответе. Остальные — CSP, защита от встраивания, Referrer-Policy и Permissions-Policy — действуют на документы, так что важны HTML-страницы. Отправлять их везде не вредно.