Перейти к содержанию
Innovialabs

Content Security Policy (CSP): практическое руководство по настройке

Продвинутое4 мин

Коротко

CSP — заголовок ответа, в котором перечислено, какие скрипты и другие ресурсы браузер может выполнять на странице; самый надёжный вариант — политика на nonce со strict-dynamic, к которой приходят постепенно через режим report-only, чтобы ничего не сломать.

Коротко

Content Security Policy — заголовок, который сообщает браузеру, откуда разрешено загружать скрипты, стили, изображения, фреймы и сетевые запросы. Если злоумышленник через XSS-уязвимость внедрит <script> в страницу, хорошая политика не даст браузеру его выполнить.

Политики бывают двух видов:

  • Allowlist — список доверенных доменов (script-src 'self' cdn.example.com). Легко начать, но такую политику часто обходят через JSONP-эндпоинт или старую библиотеку на разрешённом домене.
  • Строгая (strict) — каждый разрешённый скрипт несёт nonce или совпадает с хешем. Обойти гораздо сложнее, это и есть рекомендуемая цель.

Основные директивы

ДирективаЧто контролируетТипичное значение
default-srcЗапасное значение для незаданных fetch-директив'self'
script-srcJavaScriptNonce + 'strict-dynamic'
style-srcCSS'self', на старте часто с 'unsafe-inline'
img-src, font-src, media-srcКартинки, шрифты, медиа'self' плюс ваш CDN, при необходимости data:
connect-srcfetch, XHR, WebSocket, отправка данных аналитики'self' плюс ваш API и адреса аналитики
frame-srcВстраиваемые вами iframeТолько используемые сервисы (карты, видео, платежи)
frame-ancestorsКто может встраивать вашу страницу'self' или 'none'
object-srcПлагины эпохи Flash'none'
base-uriТег <base>'none' или 'self'
form-actionКуда можно отправлять формы'self' плюс платёжные сервисы

Обратите внимание: frame-ancestors не наследует значение default-src и не работает в теге <meta> — только в HTTP-заголовке.

Nonce и хеши

Nonce: сервер генерирует новое случайное значение для каждого ответа и ставит его и в заголовок, и в каждый легитимный тег script.

Content-Security-Policy: script-src 'nonce-R4nd0mBase64' 'strict-dynamic'; object-src 'none'; base-uri 'none'
<script nonce="R4nd0mBase64" src="/app.js"></script>

У внедрённого скрипта правильного nonce нет, и он не выполнится. Nonce должен быть непредсказуемым и разным в каждом ответе — зашитый в код nonce бесполезен. Поэтому nonce подходит для страниц с серверным рендерингом; полностью статичный закэшированный HTML не может нести свежее значение.

Хеш: для статичных inline-скриптов можно разрешить 'sha256-...' от их точного содержимого. Любое изменение скрипта, даже пробел, меняет хеш. Хеши подходят статическим сайтам.

'strict-dynamic' позволяет доверенному скрипту подгружать другие скрипты без перечисления всех доменов. Браузеры, которые его поддерживают, игнорируют списки хостов в script-src, поэтому https: можно оставить как запасной вариант для старых браузеров.

Режим report-only

Content-Security-Policy-Report-Only: default-src 'self'; report-to csp
Reporting-Endpoints: csp="https://example.com/csp-reports"

Браузер фиксирует нарушения и отправляет отчёты, но ничего не блокирует. Некоторым браузерам всё ещё нужна старая директива report-uri, поэтому многие сайты отправляют обе. Будьте готовы к шуму: расширения браузера и внедрённые панели создают отчёты, которые к вам не относятся.

Сторонние скрипты и аналитика

  • Аналитика, чат-виджеты, пиксели загружают свои скрипты и затем отправляют данные на свои домены. Обычно им нужны записи в script-src (или nonce на загрузчике вместе со strict-dynamic), connect-src и img-src. Список доменов берите из документации сервиса и сверяйте с отчётами.
  • Менеджеры тегов, в которые маркетологи вставляют произвольный HTML, по сути разрешают любой код. Ограничьте круг тех, кто может публиковать теги.
  • Inline-обработчики (onclick="...") и ссылки javascript: строгая политика блокирует. Замените их на addEventListener.
  • eval и new Function требуют 'unsafe-eval'. Найдите библиотеку, которая их использует, и обновите или замените её, вместо того чтобы разрешать eval глобально.

От мягкой политики к строгой — по шагам

  1. Инвентаризация. Откройте основные типы страниц с DevTools и выпишите все внешние скрипты, стили, шрифты, фреймы и API-хосты.
  2. Базовая политика в report-only. Выкатите report-only политику, близкую к увиденному. Соберите отчёты на реальном трафике за какое-то время.
  3. Сначала включите безопасные директивы: object-src 'none', base-uri 'none', frame-ancestors 'self', form-action 'self'. Они редко что-то ломают.
  4. Почистите код: вынесите inline-скрипты в файлы или дайте им nonce, уберите inline-обработчики и eval.
  5. Переведите script-src на nonce + 'strict-dynamic' в report-only, исправьте найденное, затем включите блокировку.
  6. Ужесточайте стили и остальное позже и продолжайте следить за отчётами после каждого релиза.

Итоговый заголовок проверьте в Google CSP Evaluator и в консоли браузера.

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

Частично. Политика в <meta http-equiv> работает для большинства директив, но не для frame-ancestors, отчётов и sandbox. HTTP-заголовок предпочтительнее.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.