Контент-безопасность сайта: настройка CSP, риски обхода и внедрение
1. Что именно блокирует CSP
CSP передаёт правила через HTTP-заголовок ответа, и браузер по ним решает, какие ресурсы можно загружать. Она защищает в основном от двух проблем: вредоносных скриптов, внедрённых через XSS, и неавторизованных сторонних доменов, встроенных в страницу.
Это не файрвол. CSP не защищает от SQL-инъекций и от взлома сервера — она управляет загрузкой ресурсов на стороне браузера. С HTTPS и HSTS она работает в связке; о настройке HTTPS читайте в статье Настройка HTTPS для собственного сайта.
Частая проблема собственных сайтов — слишком много сторонних скриптов: GA4, GTM, Hotjar, онлайн-чат, рекламные пиксели. С каждым новым скриптом в белый список CSP нужно добавлять домен, иначе браузер заблокирует скрипт. О работе со сторонними скриптами читайте в разделе Работа со сторонними скриптами на собственном сайте.
Часто упускают из виду, что CSP почти не влияет на SEO. Googlebot сам по себе не применяет ограничения на ресурсы помимо JS, и правильно настроенная CSP не влияет на индексацию. Бояться, что CSP помешает сканированию, излишне.
| Директива | Что управляет | Рекомендуемое значение |
|---|---|---|
| default-src | Резерв для всех ресурсов | ‘self’ |
| script-src | Источники JS-скриптов | ‘self’ + домены аналитики |
| style-src | Источники CSS | ‘self’ ‘unsafe-inline’ |
| img-src | Источники изображений | ‘self’ data: + CDN |
| object-src | Плагины | ‘none’ (обязательно отключить) |
| frame-ancestors | Кто может встраивать сайт | ‘none’ |
2. Три этапа внедрения: отчёт, сужение, принуждение
Самая частая ошибка — сразу включить заголовок Content-Security-Policy. Так вы мгновенно заблокируете все скрипты, не попавшие в белый список: GA4, онлайн-чат, пиксели — всё отвалится, и бизнес сразу пожалуется.
Правильный подход — сначала использовать заголовок Content-Security-Policy-Report-Only. Браузер только сообщает о нарушениях, не блокирует их; вы собираете логи две недели и добавляете в белый список все реально используемые домены.
Пример сценария 1: добавляем CSP на сайт WordPress, сначала отправляем Report-Only.
Через два дня в отчёте появляются три домена: googletagmanager.com, hotjar.com, youtube.com.
Добавляем их по одному в script-src и frame-src. Через две недели в отчёте остаются единицы в день — переключаемся на принудительный режим.
| Этап | Заголовок ответа | Действие | Длительность |
|---|---|---|---|
| Первый шаг | Content-Security-Policy-Report-Only | Только отчёт, без блокировки | 14 дней |
| Второй шаг | То же, постепенно дополняем белый список | Нарушений всё меньше | Пока не останутся единицы |
| Третий шаг | Content-Security-Policy | Настоящая блокировка | Долгосрочный |
| Весь процесс | Добавляем report-to или report-uri | Собираем отчёты о нарушениях | Работает постоянно |
Встроенные скрипты — главная ловушка. Темы WordPress и многие плагины пишут <script> прямо в страницу, а CSP по умолчанию блокирует встроенные скрипты. Есть два способа: вынести встроенный скрипт во внешний js-файл или разрешить конкретный фрагмент через хеш sha256, а не открывать unsafe-inline ради удобства.
style-src мягче, чем script-src. Редактор Гутенберга и многие темы выводят встроенные стили, поэтому временно оставить unsafe-inline в style-src не страшно.
А вот в script-src unsafe-inline открывать нельзя ни в коем случае — это главная линия защиты CSP от XSS. К строгости этих двух директив нужно подходить по-разному.
Три. Риски обхода: не дайте CSP стать пустой формальностью
Ошибка в настройке CSP равносильна её отсутствию. Три приведённых ниже варианта полностью обнуляют эту защиту.
Первый — открыть unsafe-inline. Это разрешает выполнение любого встроенного скрипта, включая вредоносный код, внедрённый через XSS, и главная линия защиты CSP рушится. Второй — открыть подстановочный знак *, то есть не ограничивать источники вообще. Третий — забыть про object-src, и тогда старые браузеры всё ещё смогут обойти защиту через Flash или плагины.
Пример второй: ради удобства в script-src прописали ‘unsafe-inline’.
Через два месяца на сайт внедрили встроенный фишинговый скрипт, и CSP его полностью пропустил.
Правильный подход — nonce: для каждого запроса генерируется одноразовое случайное число, и разрешаются только встроенные скрипты с этим числом.
| Неправильный вариант | Риск | Альтернатива |
|---|---|---|
| script-src * | Загрузится любой сторонний скрипт | Перечислить домены белого списка по одному |
| script-src ‘unsafe-inline’ | Внедрённый через XSS скрипт выполнится как обычно | Вынести во внешний файл или использовать nonce/хеш |
| Не указан object-src | Старые плагины можно использовать во вред | object-src ‘none’ |
| Не настроен frame-ancestors | Страницу встраивают во вредоносный iframe | frame-ancestors ‘none’ |
CSP и соответствие требованиям — разные вещи. Согласие на Cookie по GDPR — отдельный механизм, см.Соответствие GDPR для собственного сайта. CSP защищает от внедрения скриптов и не решает вопрос согласия на Cookie. О входе на уровне сервера и правах на файлы см.Усиление безопасности собственного сайта.
Как выбрать между nonce и хешем? Для фиксированных встроенных скриптов используйте хеш, для динамически генерируемых — nonce. Фиксированный фрагмент кода отслеживания в теме WordPress разрешается по хешу; блок скрипта, содержимое которого меняется при каждом запросе, — по nonce.
Место развёртывания тоже имеет значение. Заголовок ответа CSP лучше отдавать на уровне Nginx или CDN, а не через PHP-приложение. Вывод на уровне приложения теряется при сбое PHP, а на уровне CDN работает стабильно.
| Место развёртывания | Стабильность | Когда подходит |
|---|---|---|
| Конфигурация Nginx | Высокая, работает даже при падении приложения | Собственный сервер |
| Правила CDN | Высокая, действует по всему миру | Используется Cloudflare и подобные |
| Вывод через PHP-приложение | Низкая, теряется при сбое | Временное тестирование |
Конечную точку для приёма отчётов тоже нужно создать отдельно. Адрес, на который указывает report-uri, не должен блокироваться самим CSP, иначе отчёты не будут отправляться.
Для собственного сайта можно использовать готовый сервис сбора отчётов или настроить в Nginx отдельный log endpoint для приёма отчётов с записью в отдельный лог-файл, не попадающий в основной лог трафика.
Когда отчётов становится много, их нужно агрегировать. Ежедневно может приходить несколько сотен отчётов — просмотреть каждый по отдельности нереально. Группируйте по нарушенной директиве и заблокированному домену, особое внимание уделяйте новым доменам, появляющимся каждый день.
Есть распространённое заблуждение: кажется, что после включения CSP всё в порядке. На самом деле каждый новый сторонний скрипт, каждое обновление темы могут добавить новый источник нарушений.
Поэтому после перехода в Enforce нельзя отключать report-uri. Это единственный источник сигналов о том, что «новый скрипт забыли добавить в белый список».
Стоит упомянуть и поддержку браузеров. Современные браузеры (Chrome, Edge, Firefox, Safari за последние два года) полностью поддерживают CSP 3. Старый IE можно игнорировать — посетители B2B-сайтов им практически не пользуются.
Ещё одна деталь развёртывания: заголовок CSP нужно отдавать и по http, и по https. Если отдавать только по https, запросы на этапе обращения к origin по http останутся без защиты CSP.
После перехода в Enforce рекомендуется на некоторое время оставить параллельно заголовок Report-Only: он и блокирует, и отправляет отчёты — если что-то упустите, увидите это в отчётах.
CSP — не разовый проект. Каждый новый сторонний скрипт (чат поддержки, чат, видео, реклама) сначала нужно наблюдать в режиме отчётов, убедиться, что он появляется в нарушениях, затем добавить соответствующий домен в белый список и только потом переходить в production.
Добавьте проверку CSP в чек-лист перед запуском нового скрипта: добавляете скрипт — сразу добавляйте белый список. Не ждите, пока после запуска пользователи сообщат, что «виджет не отображается», чтобы вспомнить о CSP.
Критерий для оценки: если сторонний скрипт нельзя вынести во внешний файл и нельзя вычислить его хеш, стоит пересмотреть, нужен ли вообще этот сторонний сервис. Сервисы, зависящие от unsafe-inline, — это технический долг.
После развёртывания проверьте страницы через панель Security в консоли браузера. Просмотрите главную, страницу товара, страницу блога и страницу с формой — убедитесь, что нет красных предупреждений о нарушениях.
Для страниц, не прошедших проверку, найдите соответствующий домен в отчётах, добавьте его в белый список и понаблюдайте ещё день. Повторите этот цикл два-три раза — и всё стабилизируется.
CSP — это не разовая задача по безопасности, которая заканчивается после развёртывания. Она развивается вместе с вашей библиотекой сторонних скриптов. Каждый новый скрипт требует синхронного обновления белого списка — ни в коем случае не пропускайте ни одного нового домена сторонних скриптов.
| Источник отчёта | Значение | Что делать |
|---|---|---|
| Нарушение script-src | Новый скрипт не добавлен в белый список | Добавить домен или вынести во внешний файл |
| Нарушение img-src | Внешняя ссылка на изображение с нового домена | Добавить CDN или изменить на относительный путь |
| Нарушение frame-src | Встроен новый iframe | Добавить домен видео или убрать |
| Нарушение base-uri | На страницу внедрён тег base | Немедленно проверить на XSS |
FAQ: частые вопросы
Стоит ли сразу включать CSP в принудительном режиме?
Что делать, если CSP блокирует встроенные скрипты в теме WordPress?
Как связаны CSP, HTTPS и HSTS?
Почему в моих отчётах CSP одни нарушения?
Может ли CSP защитить от SQL-инъекций?
Нужно ли ещё оставлять report-uri?
В заключение
Сложность CSP не в написании правил, а в терпеливом прогоне режима отчётов. Две недели наблюдения в обмен на управляемость скриптов в течение нескольких лет — выгодная сделка.
Три шага к внедрению: сначала запустить Report-Only на 14 дней; по отчётам добавлять реальные домены в белый список по одному и заменить unsafe-inline хешами; когда нарушений останутся единицы, переключиться на принудительный режим и оставить report для постоянного мониторинга.
Фраза, которую стоит запомнить: уровень безопасности CSP зависит от того, насколько чистый белый список, — если оставить unsafe-inline и подстановочные знаки, эта стена ничего не стоит.
