Content Security Policy (CSP): практическое руководство по настройке
Продвинутое4 минРедакция Innovialabs
Коротко
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-src | JavaScript | Nonce + 'strict-dynamic' |
style-src | CSS | 'self', на старте часто с 'unsafe-inline' |
img-src, font-src, media-src | Картинки, шрифты, медиа | 'self' плюс ваш CDN, при необходимости data: |
connect-src | fetch, 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 глобально.
От мягкой политики к строгой — по шагам
- Инвентаризация. Откройте основные типы страниц с DevTools и выпишите все внешние скрипты, стили, шрифты, фреймы и API-хосты.
- Базовая политика в report-only. Выкатите report-only политику, близкую к увиденному. Соберите отчёты на реальном трафике за какое-то время.
- Сначала включите безопасные директивы:
object-src 'none',base-uri 'none',frame-ancestors 'self',form-action 'self'. Они редко что-то ломают. - Почистите код: вынесите inline-скрипты в файлы или дайте им nonce, уберите inline-обработчики и
eval. - Переведите
script-srcна nonce +'strict-dynamic'в report-only, исправьте найденное, затем включите блокировку. - Ужесточайте стили и остальное позже и продолжайте следить за отчётами после каждого релиза.
Итоговый заголовок проверьте в Google CSP Evaluator и в консоли браузера.