Настройка CSP (Content Security Policy) для сайта
Content Security Policy — заголовок, который сообщает браузеру, откуда разрешено загружать скрипты, стили, шрифты, изображения и другие ресурсы. Грамотно настроенный CSP делает XSS-атаки практически бесполезными: даже если атакующий внедрит вредоносный скрипт, браузер его заблокирует.
Анатомия директив
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.example.com 'nonce-{RANDOM}';
style-src 'self' https://fonts.googleapis.com 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com wss://ws.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
Ключевые директивы:
| Директива | Контролирует |
|---|---|
script-src |
Откуда загружаются JS-скрипты |
style-src |
Откуда загружаются CSS |
img-src |
Источники изображений |
connect-src |
XHR, fetch, WebSocket |
frame-ancestors |
Кто может встраивать страницу в iframe |
form-action |
Куда отправляются формы |
Nonce-based подход
'unsafe-inline' для скриптов сводит CSP на нет. Вместо него используют nonce — случайное значение, генерируемое на сервере для каждого запроса:
// PHP/Laravel
$nonce = base64_encode(random_bytes(16));
header("Content-Security-Policy: script-src 'self' 'nonce-{$nonce}'");
// В шаблоне
<script nonce="{{ $nonce }}">
// этот скрипт пройдёт проверку
</script>
В Next.js это реализуется через middleware:
// middleware.ts
import { NextResponse } from 'next/server';
import crypto from 'crypto';
export function middleware(request: Request) {
const nonce = crypto.randomBytes(16).toString('base64');
const csp = `script-src 'self' 'nonce-${nonce}'; ...`;
const response = NextResponse.next();
response.headers.set('Content-Security-Policy', csp);
response.headers.set('x-nonce', nonce);
return response;
}
Режим Report-Only
Перед принудительным применением CSP запускают в режиме наблюдения:
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
Браузер отправляет JSON-отчёты о нарушениях, не блокируя контент. Анализ отчётов за 1–2 недели показывает, какие источники нужно добавить в whitelist.
Пример отчёта:
{
"csp-report": {
"document-uri": "https://example.com/page",
"violated-directive": "script-src-elem",
"blocked-uri": "https://evil.com/payload.js",
"disposition": "report"
}
}
Сложности с SPA и CDN
React/Vue/Angular приложения часто используют eval() или динамическую генерацию скриптов через webpack. Это создаёт конфликт с CSP. Решения:
-
Webpack:
devtool: 'source-map'вместоeval, настройкаTrustedTypes -
Google Analytics / GTM: добавить
https://www.google-analytics.comиhttps://www.googletagmanager.comвscript-srcиconnect-src -
Inline-стили из JS-библиотек: использовать
style-srcс'unsafe-inline'только если альтернатив нет, или перейти на CSS-классы
Настройка в Nginx
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com; ..." always;
Для динамических nonce лучше управлять заголовком на уровне приложения, а не Nginx.
Мониторинг нарушений
Подключают endpoint для сбора CSP-отчётов или используют сторонние сервисы: Report URI, Sentry (поддерживает CSP reporting). Это позволяет отловить легитимные источники, которые забыли добавить в политику, и отследить реальные попытки XSS.
Типичные ошибки
-
'unsafe-inline'и'unsafe-eval'вscript-src— отменяют защиту от XSS - Wildcard
*вdefault-src— то же самое - Отсутствие
frame-ancestors— сайт уязвим к Clickjacking - Забыть
wss://вconnect-srcпри использовании WebSocket
Срок реализации
- Аудит текущих источников ресурсов: 2–4 часа
- Настройка Report-Only + сбор данных: 1–2 недели
- Переход в enforcement-режим с отладкой: 3–5 дней







