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.