Content Security Policy (CSP) — HTTP-заголовок, который сообщает браузеру, откуда разрешено загружать скрипты, стили, изображения и другие ресурсы. Это эффективная защита от XSS-атак и data injection.
Базовые директивы CSP
| Директива | Контролирует |
|---|---|
| default-src | Источник по умолчанию для всех ресурсов |
| script-src | JavaScript |
| style-src | CSS |
| img-src | Изображения |
| connect-src | AJAX, WebSocket, Fetch |
| font-src | Шрифты |
| frame-src | iframes |
| report-uri | URL для отчётов о нарушениях |
Примеры CSP политик
Строгая политика (для простых сайтов)
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'
Для WordPress с Google Fonts и GTM
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' data: https://www.google-analytics.com;
Настройка через .htaccess
<IfModule mod_headers.c>
Header always set Content-Security-Policy " default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self';"
</IfModule>
Режим Report-Only для тестирования
# Не блокирует, только присылает отчёты
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report.php
Content-Security-Policy-Report-Only — собирайте нарушения неделю, затем переходите на строгий CSP без поломки сайта.'unsafe-inline' и 'unsafe-eval' ослабляют защиту. Используйте nonce или hash для инлайн-скриптов в production.Разбор сообщений о нарушении CSP
Браузер всегда печатает в консоли, какая именно директива сработала и что заблокировано — не нужно гадать.
Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution.— инлайн-скрипт в HTML заблокирован; решение — вынести код в отдельный файл или подписать блок nonce/hash.Refused to load the stylesheet 'https://fonts.googleapis.com/...' because it violates the following Content Security Policy directive: "style-src 'self'".— домен шрифтов не добавлен вstyle-src, стили с внешнего CDN не подключатся.Refused to connect to 'https://api.example.com/...' because it violates the following Content Security Policy directive: "connect-src 'self'".— AJAX/fetch-запрос к стороннему API заблокирован; домен нужно явно перечислить вconnect-src.Refused to frame 'https://widget.example.com/' because it violates the following Content Security Policy directive: "frame-src 'self'".— встроенный iframe стороннего виджета не откроется, пока его домен не появится вframe-src.- JSON-отчёт на
report-uriвида{"csp-report": {"document-uri": "...", "violated-directive": "script-src-elem", "blocked-uri": "...", "line-number": 42}}— так браузер сообщает о нарушении на сервер: видно, какая строка кода и какой ресурс вызвали блокировку, даже если пользователь не открывал консоль.
Способы задать CSP: где и когда
| Способ | Где задаётся | Поддержка frame-ancestors | Когда использовать |
|---|---|---|---|
| HTTP-заголовок Content-Security-Policy | Ответ сервера (Nginx, Apache, PHP) | Да, полностью | Основной способ в production |
| Тег <meta http-equiv> в HTML | Внутри <head> страницы | Нет, директива игнорируется в meta-теге | Когда нет доступа к серверным заголовкам, например на статическом хостинге без конфигурации |
| Content-Security-Policy-Report-Only | Отдельный заголовок ответа сервера | Да | Тестовый режим: собирает нарушения, но ничего не блокирует |
| Одновременно enforce и report-only | Два заголовка сразу | Да | Постепенное ужесточение действующей политики со сбором данных о новых нарушениях |
Когда включать строгий CSP рано
- В коде много инлайн-обработчиков вида
onclick="..."и разбросанных<script>без атрибутов — строгийscript-srcбез'unsafe-inline'сломает их все разом; нужен постепенный перевод на nonce или hash, а не мгновенное включение. - На сайте стоят сторонние виджеты — чат поддержки, платёжная форма, рекламная сеть, — которые сами внедряют инлайн-код и тянут ресурсы с непредсказуемых доменов; их источники придётся вручную добавлять в политику и периодически обновлять список.
- Сайт раздаётся через несколько CDN с часто меняющимися поддоменами — фиксированный список доменов в CSP быстро устаревает и требует постоянной ручной синхронизации.
- Нет возможности собирать отчёты о нарушениях — включение строгой политики без периода
Report-Onlyпочти гарантированно ломает часть функциональности незаметно для администратора, пока не пожалуются пользователи.
Проверка, что политика реально применяется
# Убедиться, что заголовок действительно приходит от сервера
curl -I https://example.com | grep -i content-security-policy
# Открыть DevTools → Console на боевой странице:
# строк "Refused to" быть не должно, если политика в режиме enforce
Для проверки самой политики на логические ошибки (слишком широкие источники, отсутствие object-src) удобно вставить значение заголовка в независимый онлайн-анализатор CSP, например CSP Evaluator от Google. Если параллельно настроен report-uri, отчёты о попытках нарушения приходят даже когда политика уже в режиме enforce, а не только на этапе Report-Only.