{"index":3,"slug":"editorial-2027-12-practice-author-manifesto","title":"Security headers: CSP и HSTS без иллюзии защиты","excerpt":"Как CSP и HSTS ограничивают браузер, почему nonce и HTTPS не заменяют исправление XSS и как вводить политики с проверяемым откатом.","contentHtml":"
Проблема становится видимой после XSS или downgrade-атаки: приложение отдаёт страницу по HTTPS, но разрешает произвольный inline-скрипт или продолжает открываться по HTTP. Симптомы можно увидеть в DevTools: CSP фиксирует нарушение или отсутствует вовсе, а HTTP-запрос сначала доходит до редиректа. Цена ошибки — выполнение чужого кода в контексте origin, утечка токена и потеря данных. Один security header не закрывает весь риск.
Ошибка начинается с копирования длинной строки заголовка без карты ресурсов и момента включения политики. CSP управляет тем, откуда страница может загружать и выполнять ресурсы. HSTS говорит браузеру обращаться к домену по HTTPS после получения политики через доверенный HTTPS-ответ. Ни один заголовок не исправляет серверный XSS, плохой сертификат, mixed content или секрет, который уже попал в JavaScript.
Политику нужно проектировать от конкретной страницы. Сначала назовите нужные скрипты, стили, изображения, API и фреймы. Затем разрешите только эти источники и отдельно проверьте отрицательные сценарии. Так CSP становится исполняемым контрактом браузера, а не декоративной строкой в конфигурации.
default-src задаёт запасное правило для типов ресурсов, но не заменяет явные директивы. script-src управляет JavaScript. object-src 'none' закрывает загрузку plugin/object, если она не нужна. base-uri 'self' ограничивает изменение базового URL. Такая политика уменьшает поверхность атаки, но не превращает небезопасный sink в безопасный.
Браузер получает Content-Security-Policy вместе с ответом и сопоставляет каждую попытку загрузки или выполнения с директивой. При несовпадении ресурс блокируется в режиме enforce. В режиме Report-Only браузер отправляет нарушение для анализа, но не блокирует действие. Поэтому отчёт помогает собрать карту, но сам по себе не является исправлением.
Nonce нужен для конкретного inline-скрипта, который пока нельзя вынести в файл. Сервер генерирует непредсказуемое значение заново для каждого ответа, вставляет его в CSP и в атрибут нужного script. Статическая строка не даёт защиты: тот, кто её узнает, получает разрешение. Полный nonce не стоит писать в диагностические логи.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
| Inline-скрипт заблокирован | Нет nonce или разрешение слишком узкое | Сверить директиву script-src и атрибут nonce | Вынести код в файл или выдать свежий nonce только этому скрипту |
| В отчёте много нарушений | Политика собрана без карты ресурсов | Сгруппировать отчёты по директиве, URL и типу ресурса | Удалить лишние источники и обосновать каждый оставшийся |
| Сайт всё ещё доступен по HTTP | Браузер ещё не получил HSTS или первый переход был HTTP | Проверить заголовок в HTTPS-ответе и цепочку переходов | Исправить HTTPS и redirect; отдельно оценить требования preload |
| Поддомен перестал открываться | includeSubDomains включили до проверки всех имён | Проверить сертификаты, HTTPS и endpoint каждого поддомена | Расширять область и max-age только после контролируемой проверки |
| После CSP остаётся XSS | Небезопасный HTML или sink не исправлен | Проверить контекстное экранирование и места вроде innerHTML | Исправить источник XSS; CSP оставить дополнительным барьером |
Ниже — учебная функция, которая собирает заголовки из nonce и срока HSTS. Она не устанавливает заголовки в серверном ответе и не проверяет шаблонизатор. Проверяемый результат показывает только форму политики: nonce присутствует в CSP, а HSTS содержит числовой срок и includeSubDomains.
import { buildSecurityHeaders } from './security-headers.mjs'; const result = buildSecurityHeaders({ nonce: '7c2f1b8e9a4d6f0c', hstsMaxAge: 31536000 }); if (!result.ok) throw new Error(result.reason); response.setHeader('Content-Security-Policy', result.headers['Content-Security-Policy']); response.setHeader('Strict-Transport-Security', result.headers['Strict-Transport-Security']);В реальном шаблоне nonce должен попасть только в разрешённый inline-скрипт:
<script nonce=\"7c2f1b8e9a4d6f0c\">startApplication();</script>Пример не доказывает, что приложение безопасно. Он проверяет сборку значения и показывает границу между генерацией заголовка и его применением. Тесты должны отдельно проверить отсутствие случайного unsafe-inline, соответствие nonce в двух местах и отправку заголовков только по нужному маршруту.
HSTS начинает действовать после того, как браузер получил Strict-Transport-Security через доверенный HTTPS-ответ. Большой max-age не защищает самый первый HTTP-переход, если домен ещё неизвестен браузеру. Он также не исправляет сертификат или mixed content. Для защиты первого перехода существует preload с отдельными требованиями и риском: сначала нужно проверить готовность домена.
includeSubDomains распространяет правило на поддомены. Включайте его только после проверки всех имён, которые ещё нужны пользователям, API и административным инструментам. Если один поддомен не умеет HTTPS, браузер перестанет подключаться к нему по HTTP на весь срок политики. Возможность отката — часть безопасности, а не последняя строка runbook.
max-age и область.Nonce не санитизирует пользовательский HTML и не исправляет небезопасный sink. Если приложение передаёт строку в innerHTML, разрешённый bootstrap может помочь атакующему выполнить уже загруженный код. Нужны контекстное экранирование и безопасные API. CSP снижает последствия и обнаруживает часть нарушений, но не заменяет исправление причины.
Эта схема не проверяет совместимость целевых браузеров, CDN, service worker, iframe-политику, сертификаты, preload и настройки API-клиента. CSP Level 3 остаётся Working Draft, поэтому директивы и поддержку следует сверять с целевыми браузерами. RFC 6797 описывает HSTS, но не даёт защиты до первого безопасного ответа.
Готовность наступает, когда для конкретной страницы есть карта ресурсов, Report-Only нарушения разобраны, enforce проверен, HSTS подтверждён на каждом нужном домене, а откат воспроизводится. Дополнительный признак — автоматическая проверка заголовков ловит возврат широкого источника или исчезновение обязательной политики. Без этих проверок строка конфигурации остаётся предположением.