Content Security Policy (CSP)
CSP
Что это такое
Content Security Policy — это набор правил, которые сервер посылает вместе со страницей и которые говорят браузеру, что эта страница вправе загружать и выполнять: какие источники могут отдавать скрипты, разрешены ли встроенные скрипты и стили, куда страница может отправлять данные форм и подключаться и можно ли встраивать её во фрейм. Политику исполняет браузер; внедрённый скрипт атакующего не голосует.
Почему это важно
Экранирование вывода — основная защита от внедрения скриптов, и иногда оно даёт сбой: шаблон с лазейкой, санитайзер с пропуском, сторонний виджет, который никто не проверял. CSP — тот слой, который превращает такой сбой из компрометации сессии в заблокированный запрос и сообщение в консоли. Строгая политика ещё и делает сторонний код видимым: каждый новый источник скриптов приходится добавлять осознанно, а это ровно то ревью, которое вам и нужно.
Ключевые идеи
Начните с политики «запретить всё» и откройте нужное
Ограничительное значение по умолчанию — default-src 'none', дальше добавляйте источники, которые страница действительно использует. Заметьте, что 'unsafe-inline' в script-src отключает почти всю защиту — это переключатель, возвращающий политику атакующему.
Content-Security-Policy:
default-src 'none';
script-src 'self' 'nonce-<random per response>';
style-src 'self';
img-src 'self' data:;
connect-src 'self';
frame-ancestors 'none';
base-uri 'self';
Каждая строка отвечает на один вопрос: какой код может выполняться, откуда могут приходить изображения, куда можно отправлять данные и кто может встроить страницу во фрейм.
Используйте nonce вместо разрешения встроенных скриптов
Случайный nonce на каждый ответ в ваших собственных тегах <script> позволяет браузеру отличить ваш встроенный скрипт от внедрённого. Для фиксированного скрипта так же работает хеш. Именно это делает строгую политику возможной на странице, которой действительно нужен встроенный код.
script-src 'self' 'nonce-8f2b...' -> выполняются только скрипты с этим nonce
Сначала включайте в режиме только отчётов
Отправляйте Content-Security-Policy-Report-Only с эндпоинтом для отчётов, неделю собирайте нарушения и исправьте законные, прежде чем включать принуждение. Принудительно включённая политика, написанная по догадкам, ломает страницу так, что о поломках узнают пользователи.
Держите политику там, где отдаётся страница
Политика, заданная тегом meta, не покрывает всего, что покрывает заголовок, и шаблон легко уходит от неё. Отправляйте её заголовком ответа и генерируйте по странице там, где меняется nonce.
Считайте сторонние источники проверяемым списком
Каждый источник, добавленный в script-src, — это код, который выполняется на странице. Держите список коротким, с датами и владельцем.
Практика
Напишите политику для одной страницы и проверьте её в режиме отчётов
Возьмите настоящую страницу, напишите ограничительную политику, разверните её на неделю в режиме только отчётов и перечислите нарушения.
Готово, когда: вы можете объяснить каждое нарушение — либо законный источник, который надо добавить, либо ошибка, которую надо исправить, — и политика включается принудительно, не ломая страницу.
Уберите один 'unsafe-inline'
Найдите страницу, чья политика содержит 'unsafe-inline', и замените его на nonce или хеш для скриптов, которым он действительно нужен.
Готово, когда: политика работает принудительно без 'unsafe-inline' и ни один скрипт не заблокирован.
Частые ошибки
'unsafe-inline'оставлен на месте — внедрённый скрипт разрешён, и политика ничего не защищает; исправить: используйте nonce или хеш.- Принудительное включение без проверки — страница ломается у пользователей; исправить: сначала режим только отчётов и исправление законных нарушений.
- Политика задана только в теге meta — покрытие неполное и уходит вместе с шаблоном; исправить: отправляйте её заголовком ответа.
- Источники скриптов через подстановочный знак — код на страницу может отдать любой хост; исправить: называйте источники и проверяйте каждое добавление.
- Забытый
frame-ancestors— страницу можно встроить во фрейм и перехватывать клики; исправить: задайте'none', если встраивание не является функцией.
Что читать дальше
- Веб-безопасность (OWASP Top 10) (07-security/01-web-security-owasp-top-10) — прочитайте сначала; CSP — страховка для классов внедрения, описанных там.
- HTTPS и TLS (07-security/02-https-tls) — политика осмысленна только на странице, которую не изменили по пути.
- Межсайтовый скриптинг и классы уязвимостей на стороне браузера (07-security/01-web-security-owasp-top-10/02-cross-site-scripting-and-the-browser-side-classes) — сбой, который ограничивает эта политика.
- Управление секретами (07-security/03-secrets-management) — не пускать на страницу то, до чего попытается дотянуться скрипт.