DarkRiDDeR15 мин

Security headers: CSP и HSTS без иллюзии защиты

SecurityWeb

Проблема проявляется после XSS или downgrade-атаки: приложение отдаёт страницу по HTTPS, но разрешает любой inline script или продолжает открываться по HTTP. Цена ошибки — выполнение чужого кода в контексте origin, утечка токена и ложное ощущение, что один security header закрыл весь риск.

Причина — копировать длинную строку заголовка без модели ресурсов. CSP ограничивает, откуда браузер может загружать или выполнять ресурсы; HSTS заставляет браузер обращаться к домену по HTTPS после получения политики. Ни один из заголовков не исправляет серверный XSS, плохой сертификат или секрет, уже попавший в JavaScript.

CSP начинается с карты ресурсов

Сначала перечислите, что странице действительно нужно: собственные scripts, стили, изображения, API и frame. Затем для каждого типа выберите минимальную директиву. default-src задаёт fallback, но не объясняет исключения; script-src управляет JavaScript, object-src none закрывает старый plugin-механизм, а base-uri self не даёт странице незаметно изменить базовый URL.

Nonce применяют к конкретному inline script, когда убрать inline-код сразу нельзя. Значение должно быть непредсказуемым и новым для ответа; статическая строка превращается в разрешение для любого, кто её узнал. Шаблон должен вставить nonce и в CSP, и в атрибут script, а логирование полного значения создаёт лишний риск.

Директива и её граница
ДирективаЧто ограничиваетЧастая ошибкаПроверка
default-srcfallback для типов ресурсовсчитать её полной политикойпроверить исключения по типам
script-srcисточники JavaScript и nonceдобавить unsafe-inline навсегданайти все inline и third-party scripts
object-srcplugin/object загрузкуоставить широкое значениепоставить none, если object не нужен
base-uriизменение базового URLзабыть директивуограничить self или отключить
report-onlyнаблюдение нарушенийпринять отчёт за блокировкупосле анализа перейти к enforce

HSTS имеет момент включения

HSTS действует после того, как браузер получил заголовок через доверенный HTTPS-ответ. Он не защищает самый первый HTTP-переход, если домен ещё не известен браузеру; для этого существует отдельная политика preload с собственными требованиями и риском. includeSubDomains распространяет правило на поддомены, поэтому включать его можно только после проверки всех нужных имён.

Большой max-age нельзя трактовать как кнопку «попробовать». Если поддомен ещё не умеет HTTPS, браузер перестанет подключаться к нему по HTTP на весь период. Перед расширением политики проверьте redirect, сертификаты, mixed content и административные endpoint. Безопасность заголовка включает и возможность восстановить ошибочную конфигурацию.

Граф security headers: карта ресурсов формирует CSP, HTTPS-ответ включает HSTS, а неизвестный script или неподготовленный поддомен останавливает расширение политики.
Схема показывает два независимых слоя. CSP управляет ресурсами страницы, HSTS — схемой соединения; один заголовок не заменяет другой.

Runnable-пример: собрать минимальные headers

Функция принимает nonce длиной не менее 16 символов и max-age HSTS. Она возвращает набор заголовков или явную ошибку входа. Это учебный генератор: он не устанавливает response headers и не проверяет ваш шаблонизатор. Ожидаемый результат показывает, что CSP содержит nonce, а HSTS — числовой срок и includeSubDomains.

import { buildSecurityHeaders } from './upgrade-2027-12.mjs';

const result = buildSecurityHeaders({
  nonce: '7c2f1b8e9a4d6f0c',
  hstsMaxAge: 31536000,
});

console.log(result.ok);
console.log(result.headers['Content-Security-Policy']);
console.log(result.headers['Strict-Transport-Security']);
// true
// default-src 'self'; script-src 'self' 'nonce-7c2f1b8e9a4d6f0c'; object-src 'none'; base-uri 'self'
// max-age=31536000; includeSubDomains

Порядок внедрения

  1. Соберите список ресурсов страницы и найдите inline scripts, eval, object, iframe, внешние CDN и API. Не начинайте с копирования чужой политики.
  2. Включите CSP в Report-Only и соберите нарушения по URL, директиве и типу ресурса. Отчёт не блокирует выполнение, поэтому не называйте его исправлением.
  3. Уберите лишние источники, замените inline-код на файл или nonce и добавьте тест на отсутствие unsafe-inline и unsafe-eval без обоснования.
  4. Переведите CSP в enforce на одной проверяемой странице и сравните ошибки загрузки с разрешённым списком.
  5. Включите HSTS только после проверки HTTPS для основного домена и поддоменов. Начните с контролируемого max-age, затем расширяйте.
  6. Проверьте rollback конфигурации: изменение header должно быть версионируемым, а не ручной правкой в одном proxy.

Почему nonce не лечит XSS

Nonce разрешает конкретные скрипты, но не санитизирует пользовательский HTML и не исправляет небезопасный sink. Если приложение вставляет строку в innerHTML, разрешённый bootstrap может помочь атакующему выполнить уже загруженный код. CSP снижает последствия и ловит часть нарушений, но контекстное экранирование и безопасные API остаются обязательными.

Диагностические отчёты CSP тоже требуют осторожности: URL может содержать чувствительные параметры, а third-party ресурс может присылать много шума. В отчёте храните только нужные поля, ограничивайте доступ и отделяйте нарушение политики от подтверждённой уязвимости. Заголовок — контроль браузера, не verdict о безопасности приложения.

Ограничения и следующий шаг

Генератор не проверяет браузерную поддержку, CDN, service worker, iframe-политику, сертификаты и preload. CSP Level 3 — рабочая редакция W3C, поэтому конкретную совместимость и статус директивы нужно сверять с целевыми браузерами. RFC 6797 не защищает первый небезопасный переход и не заменяет TLS.

Следующий шаг — взять одну страницу, собрать Report-Only нарушения, закрыть источники по одному и добавить автоматический тест на заголовки. После enforce отдельно проверьте HSTS на каждом поддомене и храните процедуру возврата конфигурации рядом с кодом, чтобы ошибка не требовала ручной импровизации.

Проверяемые источники

  • W3C Content Security Policy Level 3 — W3C, CSP Level 3 Working Draft, опубликованная редакция спецификации. Применение: Описывает Content-Security-Policy, директивы источников, nonce и режим Report-Only/Enforce. Граница: Working Draft может изменяться; конкретную поддержку браузеров и собственные inline-скрипты нужно проверить отдельно.
  • RFC 6797 — HTTP Strict Transport Security — IETF, ноябрь 2012 года, RFC 6797, Standards Track. Применение: Определяет Strict-Transport-Security и поведение браузера после получения политики по HTTPS. Граница: Не исправляет mixed content, сертификат, redirect до первого безопасного ответа и настройки API-клиента.