Content Security Policy: настройка CSP заголовка
Как работает заголовок Content-Security-Policy, синтаксис основных директив, безопасное внедрение через Report-Only перед принудительным включением, nonce и hash для инлайновых скриптов, и типичные поломки при добавлении CSP на готовый сайт.
От какого класса уязвимостей защищает CSP
Content Security Policy — это HTTP-заголовок, объявляющий браузеру явный список источников, откуда странице разрешено загружать скрипты, стили, изображения, шрифты и другие ресурсы, а также разрешённые способы их выполнения. Идея в том, чтобы дать браузеру дополнительный, не зависящий от корректности серверного кода механизм отказа в исполнении ресурса, не входящего в явно разрешённый список, — даже если такой ресурс уже оказался внедрён в HTML-код страницы.
Основная цель CSP — межсайтовое выполнение скриптов (XSS). Уязвимость XSS сама по себе означает, что приложение недостаточно фильтрует пользовательский ввод и позволяет внедрить произвольный HTML или JavaScript в страницу, отдаваемую другим пользователям. CSP не устраняет саму уязвимость в коде приложения — эта задача остаётся за корректной фильтрацией и экранированием ввода, — но добавляет второй, независимый рубеж защиты: даже успешно внедрённый вредоносный скрипт браузер откажется исполнять, если он не соответствует заданной политике (например, является инлайновым при запрещённом unsafe-inline или загружается с домена, не входящего в разрешённый список).
Именно поэтому CSP относят к классу defense in depth — эшелонированной защиты, а не к первичной мере против XSS: правильная обработка пользовательского ввода остаётся первоочередной задачей, а CSP работает подстраховкой на случай, если в коде приложения всё же останется пропущенная уязвимость, что на практике случается регулярно даже в тщательно написанных проектах.
Синтаксис директив
CSP описывается набором директив, каждая из которых регулирует свою категорию ресурсов, разделённых точкой с запятой в одном заголовке. Директива default-src задаёт значение по умолчанию для всех категорий, для которых не указана отдельная, более специфичная директива.
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://api.example.com
Ключевые директивы: script-src ограничивает источники, откуда можно загружать и исполнять JavaScript; style-src — источники CSS; img-src — источники изображений; connect-src регулирует адреса, к которым странице разрешено обращаться через fetch, XMLHttpRequest, WebSocket и подобные механизмы. Значение 'self' разрешает загрузку только с того же домена, что и сама страница, а перечисление конкретных доменов через пробел добавляет их в список разрешённых для этой директивы.
font-src 'self' https://fonts.gstatic.com; frame-src 'none'; object-src 'none'; base-uri 'self'
Директивы frame-src и object-src, установленные в 'none', закрывают возможность встраивания страницы во фреймы с других сайтов и использования устаревших plugin-объектов вроде Flash — обе категории исторически были источником отдельных классов уязвимостей, и их полный запрет для сайтов, не нуждающихся в этой функциональности, снижает поверхность атаки без какого-либо компромисса в функциональности.
Report-Only: тестирование политики без реального блокирования
Внедрение строгой CSP сразу в режиме принудительного применения (enforcing) на уже работающем сайте — рискованная практика: заранее почти невозможно предсказать все источники ресурсов, которые страница реально использует, особенно если сайт годами обрастал сторонними виджетами, аналитическими скриптами и инлайновым кодом.
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' https://cdn.example.com; report-uri /csp-report-endpoint
Заголовок Content-Security-Policy-Report-Only заставляет браузер оценивать каждый загружаемый ресурс на соответствие заявленной политике и отправлять отчёт о каждом нарушении на указанный в report-uri эндпоинт, но не блокирует фактическую загрузку или исполнение — сайт продолжает работать в точности как раньше, а администратор получает полную картину того, что именно будет заблокировано, если включить политику по-настоящему.
Разумная последовательность внедрения: сначала развернуть Report-Only с достаточно широкой стартовой политикой, собрать отчёты о нарушениях за представительный период (как минимум несколько дней, а лучше неделю, чтобы захватить весь спектр пользовательской активности и всех используемых на сайте функций), скорректировать политику по итогам разбора реальных нарушений, и только затем заменить заголовок на настоящий, принудительно применяемый Content-Security-Policy.
server {
add_header Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-report;" always;
}
Nonce и hash: разрешение конкретного инлайнового скрипта без ослабления всей политики
Строгая CSP по умолчанию блокирует любой инлайновый JavaScript-код на странице, поскольку именно инлайновые скрипты — самый частый вектор внедрения XSS: атакующему, нашедшему уязвимость, обычно проще вставить в страницу тег script с кодом прямо внутри HTML, чем разместить отдельный файл на контролируемом сервере. Полный запрет инлайновых скриптов закрывает этот вектор целиком, но многие сайты реально используют небольшие инлайновые скрипты для инициализации (передача серверных данных в JavaScript, настройка аналитики), и полностью вынести их во внешние файлы не всегда практично.
<script nonce="8IBTHwOdqNKAWeKl7plt8g==">
window.APP_CONFIG = { userId: 42 };
</script>
Content-Security-Policy: script-src 'self' 'nonce-8IBTHwOdqNKAWeKl7plt8g=='
Nonce — это случайное значение, генерируемое сервером заново при каждой отдаче страницы (не переиспользуемое между запросами — это критично для безопасности механизма) и одновременно проставляемое в заголовок CSP и в атрибут nonce нужного тега script. Браузер исполнит только тот инлайн-скрипт, чьё значение nonce совпадает с указанным в заголовке текущего ответа, что делает механизм бесполезным для атакующего, пытающегося внедрить собственный script-тег без знания актуального на этот конкретный запрос значения nonce.
Content-Security-Policy: script-src 'self' 'sha256-abc123...=='
Hash-подход удобнее для статичного, редко меняющегося инлайн-кода: вместо генерации нового значения на каждый запрос вычисляется криптографическая сумма (обычно SHA-256) точного содержимого конкретного script-блока, и эта фиксированная сумма прописывается в CSP — любое, даже минимальное изменение содержимого скрипта потребует пересчитать и обновить hash в заголовке, что делает подход менее удобным для часто меняющегося кода, но не требует динамической генерации значения на сервере при каждом запросе.
Типичные поломки при добавлении CSP на готовый сайт
Самая частая проблема — сторонние скрипты аналитики, рекламы, чатов и виджетов, которые сами по себе загружают дополнительные ресурсы с доменов, не входящих в изначально составленный список. Консоль разработчика браузера в этом случае покажет явное сообщение о заблокированном источнике с указанием конкретного домена и директивы, которую он нарушил, что делает диагностику относительно быстрой — при условии, что кто-то реально открывает консоль и проверяет.
Вторая частая категория — инлайновые обработчики событий в разметке (onclick, onload и подобные атрибуты), которые CSP блокирует так же, как инлайновые script-теги. Правильное исправление — не ослабление политики через unsafe-inline, а перенос логики обработчика в отдельный файл скрипта, подключаемый через addEventListener, что заодно чаще всего улучшает структуру самого кода фронтенда.
Третья категория, специфичная для style-src, — инлайновые стили через атрибут style или тег style внутри страницы, которые многие CMS и визуальные конструкторы генерируют динамически без возможности легко вынести их во внешний файл. Для style-src сообщество на практике заметно чаще идёт на компромисс с unsafe-inline, чем для script-src, поскольку риск XSS через инлайновые стили существенно ниже, чем через инлайновый JavaScript, хотя полностью нулевым он тоже не является (существуют CSS-based техники утечки данных и ограниченного взаимодействия со страницей).
Диагностировать поломку эффективнее всего через связку консоли браузера (немедленная обратная связь при разработке) и report-uri эндпоинта, агрегирующего JSON-отчёты о нарушениях от всех реальных посетителей продакшен-сайта — консоль браузера видит только то, что разработчик воспроизвёл лично, тогда как report-uri ловит нарушения, специфичные для конфигураций и путей пользователей, которые разработчик не тестировал напрямую.
Сбор отчётов о нарушениях: report-uri и report-to
Директива report-uri — более старый, но всё ещё широко поддерживаемый способ указать эндпоинт, на который браузер отправит POST-запрос с JSON-телом при каждом нарушении политики, независимо от того, работает ли CSP в enforcing или в Report-Only режиме.
{
"csp-report": {
"document-uri": "https://example.com/page",
"violated-directive": "script-src 'self'",
"blocked-uri": "https://evil-or-unexpected.example/script.js",
"source-file": "https://example.com/page",
"line-number": 42
}
}
Более новая директива report-to использует отдельный заголовок Report-To для конфигурации группы получателей отчётов и в перспективе должна заменить report-uri, но на момент написания разумно указывать обе директивы одновременно для совместимости с разным набором браузеров, поддерживающих одну, но не всегда обе директивы одинаково полно.
Эндпоинт для приёма отчётов можно реализовать как простой обработчик, логирующий входящие JSON-тела в файл или отправляющий их в систему мониторинга — важно не забыть, что этот эндпоинт сам может получать заметный объём запросов на популярном сайте, особенно на переходном этапе внедрения политики, и стоит предусмотреть агрегацию повторяющихся однотипных нарушений, а не хранить каждое как отдельное неструктурированное событие.
Upgrade-Insecure-Requests и другие соседние директивы
Помимо ограничения источников ресурсов, CSP включает несколько директив, решающих смежные, но отдельные задачи. Директива upgrade-insecure-requests указывает браузеру автоматически заменять запросы по протоколу http на https для всех ресурсов страницы, что удобно при миграции старого сайта со смешанным содержимым (mixed content), где часть ссылок на изображения или скрипты годами оставались прописанными через http, хотя сам сайт давно доступен по https.
Content-Security-Policy: upgrade-insecure-requests; default-src 'self' https:
Директива block-all-mixed-content, в свою очередь, не пытается исправить смешанное содержимое автоматически, а просто блокирует загрузку любых http-ресурсов на https-странице — с точки зрения современных браузеров эта директива во многом устарела, поскольку большинство из них и так блокируют активное смешанное содержимое (скрипты, стили) по умолчанию независимо от CSP, но директива остаётся полезной документацией явного намерения для более старых браузеров и для пассивного содержимого вроде изображений.
Директива frame-ancestors решает задачу, исторически закрывавшуюся отдельным заголовком X-Frame-Options, — определяет, с каких доменов страницу разрешено встраивать во frame или iframe, что защищает от атак типа clickjacking. Значение 'none' полностью запрещает встраивание страницы в чужие фреймы, а 'self' разрешает встраивание только страницами того же домена — эту директиву стоит включать в CSP наравне с остальными, а не полагаться только на устаревающий X-Frame-Options.
От тестовой политики к безопасной продакшен-конфигурации
После сбора отчётов о нарушениях за представительный период и корректировки директив под реально используемые сайтом источники наступает момент замены заголовка Content-Security-Policy-Report-Only на настоящий Content-Security-Policy — с этого момента браузер начинает реально блокировать несоответствующие политике ресурсы, а не только сообщать о них.
Разумная практика — сохранить report-uri (или report-to) активным и после перехода в enforcing-режим: даже после тщательного тестирования сайт продолжает развиваться, добавляются новые сторонние интеграции, и поток отчётов о нарушениях в проде остаётся полезным ранним сигналом о том, что новое изменение конфликтует с действующей политикой, ещё до того, как об этом сообщат пользователи испорченной функциональностью.
Финальный практический совет — не стремиться сразу к максимально строгой политике без unsafe-inline и unsafe-eval повсюду, если это требует немедленной масштабной переработки легаси-кода: разумнее внедрить рабочую, реалистичную политику сейчас, закрывающую основные векторы, и постепенно ужесточать её по мере рефакторинга кода, чем откладывать любую защиту на неопределённый срок в ожидании идеальной конфигурации.
CSP через meta-тег и Subresource Integrity как соседняя мера защиты
Помимо HTTP-заголовка, политику CSP можно задать через meta-тег внутри head страницы — это удобно в ситуациях, когда нет прямого доступа к настройке заголовков ответа веб-сервера (например, на статичном хостинге без возможности изменить конфигурацию сервера), но у этого способа есть ограничения: часть директив, в частности frame-ancestors и параметры report-uri/report-to, через meta-тег не работают и требуют именно настоящего HTTP-заголовка.
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://cdn.example.com">
Соседняя, но отдельная от CSP мера защиты — Subresource Integrity (атрибут integrity у тегов script и link), проверяющая, что подключаемый с внешнего домена файл не был подменён на пути к браузеру, путём сверки его содержимого с заранее известной криптографической суммой. CSP разрешает или запрещает сам факт загрузки ресурса с определённого источника, тогда как SRI гарантирует, что содержимое разрешённого ресурса не было незаметно изменено — например, при компрометации самого стороннего CDN, откуда загружается библиотека.
<script src="https://cdn.example.com/lib.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"></script>
Использование CSP и SRI вместе даёт более полную защиту от компрометации через сторонние ресурсы: CSP ограничивает список доверенных источников, а SRI дополнительно гарантирует, что даже с разрешённого источника не будет загружен незаметно подменённый файл — обе меры относительно недорого внедрить для статичных, редко меняющихся сторонних зависимостей вроде библиотек, размещённых на публичных CDN.
Почему стоит выбрать нас
Быстрый запуск
Подберём конфигурацию и развернём проект в день обращения.
Надёжность
NVMe-диски, защита от DDoS и резервные копии по расписанию.
Поддержка 24/7
Дежурные инженеры на связи в любое время суток.
Настройка под ключ
Установим CMS, настроим веб-сервер, SSL и почту за вас.
Перенос без простоя
Аккуратно перенесём сайт и базу данных на новый сервер.
Прозрачные цены
Актуальные тарифы и честный подбор без навязывания.
Вопросы и ответы
От чего конкретно защищает CSP?
В первую очередь от межсайтового выполнения скриптов (XSS) — даже если злоумышленнику удалось внедрить вредоносный код в страницу через уязвимость приложения, строгая CSP не даст браузеру исполнить этот код или загрузить ресурс с недоверенного источника, если он не входит в явно разрешённый список.
Можно ли включить CSP сразу в enforcing-режиме без предварительного тестирования?
Технически можно, но на практике почти всегда приводит к неожиданной поломке легитимной функциональности сайта — сторонние виджеты, инлайновые обработчики событий, аналитические скрипты часто нарушают строгую политику, и без предварительного этапа в режиме Content-Security-Policy-Report-Only проблемы будут обнаружены только жалобами пользователей на сломанный сайт.
Чем nonce отличается от hash для инлайновых скриптов?
Nonce — это случайное значение, генерируемое заново при каждой отдаче страницы и указываемое одновременно в заголовке CSP и в атрибуте script-тега, тогда как hash — это фиксированная криптографическая сумма содержимого конкретного инлайн-скрипта, которая не меняется, пока не меняется сам код скрипта; nonce удобнее для динамически генерируемых страниц, а hash — для статичного, редко меняющегося инлайн-кода.
Почему после включения CSP сломались сторонние виджеты (карта, чат, аналитика)?
Потому что script-src или connect-src по умолчанию (или из-за директивы default-src) не включают домен, с которого сторонний виджет загружает свои дополнительные скрипты или к которому обращается через AJAX — типичное решение — явно добавить домен виджета в соответствующую директиву после того, как консоль браузера покажет, какой именно источник был заблокирован.
Что делать, если инлайновые обработчики onclick перестали работать после внедрения CSP?
Строгая CSP по умолчанию блокирует инлайновые обработчики событий вроде onclick в HTML-атрибутах — правильное решение не в ослаблении политики через unsafe-inline, а в переносе логики из инлайн-атрибутов в обработчики событий, подключаемые через addEventListener из внешнего или помеченного nonce/hash файла скрипта.
Нужно ли использовать unsafe-inline и unsafe-eval?
По возможности нет — обе директивы фактически отключают основную защиту CSP от XSS для соответствующей категории кода, и их стоит использовать только как временную меру на переходный период миграции легаси-кода, с чётким планом последующего перехода на nonce, hash или вынос кода во внешние файлы.
Как узнать о нарушениях CSP, если сайт уже в enforcing-режиме?
Через директиву report-uri или более новую report-to, указывающую эндпоинт, куда браузер автоматически отправляет JSON-отчёт о каждом заблокированном по CSP ресурсе — это позволяет собирать нарушения в проде без необходимости вручную просматривать консоль браузера каждого пользователя.
Применяется ли CSP к ресурсам, загруженным до того, как браузер получил заголовок?
Нет, CSP применяется браузером сразу при получении ответа с заголовком, до начала разбора и исполнения содержимого страницы — но важно, что заголовок должен быть частью самого HTTP-ответа (или meta-тега в head), а не устанавливаться скриптом на странице уже после её частичной загрузки, иначе часть ресурсов может успеть загрузиться до применения политики.
Как оплачивать услуги?
Оплата помесячная, для облачных серверов доступна почасовая тарификация. Принимаются карты, банковские переводы и электронные кошельки.
Есть ли круглосуточная поддержка?
Да, дежурные инженеры на связи 24/7.
Выдаёте ли закрывающие документы?
Да, предоставим необходимые документы для оплаты.
Как оставить заявку?
Заполните форму на странице или нажмите «Заказать» — мы свяжемся и поможем с подбором.