Content Security Policy (CSP) — это механизм безопасности веб-приложений, который используется для сокращения рисков, связанных с атаками, такими как внедрение скриптов (XSS) и выполнение нежелательного кода (инъекция). C SP позволяет веб-разработчикам указывать браузерам, из каких источников разрешено загружать ресурсы, такие как скрипты, стили, изображения, шрифты и другие элементы.
С помощью CSP можно определить набор допустимых источников для каждого типа ресурса, а браузеры будут блокировать попытки загрузки ресурсов из недопустимых источников. Например, вы можете настроить CSP таким образом, чтобы разрешить загрузку скриптов только из определенного домена или разрешить загрузку стилей только из локального файла.
Это помогает предотвратить атаки, основанные на выполнении вредоносного кода из внешних источников, а также уменьшает риски, связанные с подделкой источников, перехватом данных и другими видами атак. C SP является эффективным инструментом для укрепления безопасности веб-приложений и защиты пользователей от различных видов уязвимостей.
- Что дальше?
- Окей, мы разобрались с тем что такое CSP, но с чего начать, а самое главное как мы будем тестировать, работает этот механизм или нет?
- Я попробую пошагово рассказать свой опыт настройки этого механизма, и что я для этого использовал.
- Мое приложение использует стек:
- Так же есть прямой доступ к конфигу webpack с конфигурацией сборки.
- Nginx Шаг 1
- Nginx Шаг 2
- Nginx Шаг 3
- Nginx Шаг 4
- Nginx Шаг 5
- Настройка frontend приложения
- Nginx Шаг 6
- Nginx Шаг 7
- Threats
- Mitigating cross-site scripting
- Смягчение атак с перехватом пакетов
- Использование CSP
- Указание вашей политики
- Написание политики
- Примеры: общие случаи использования
- Пример 1
- Пример 2
- Пример 3
- Пример 4
- Пример 5
- Проверка вашей политики
- Включение отчетности
- Синтаксис отчета о нарушении
- Образец протокола нарушения
- Совместимость с браузером
- Примечания о совместимости
- См. также
- Содержание
- Язык политики и синтаксис
- Примеры определений политики
- предок C, но A все еще
- Список исходных выражений
- Ключевые слова исходного выражения
- Синтаксис формальной политики
- Синтаксис отчета о нарушении
- Образец отчета о нарушении
- Ни один встроенный скрипт не будет выполняться
- Код не будет создан из строк
- Нет данных: URI, если это не разрешено посредством явной политики
- Привязки XBL должны исходить из chrome: или ресурса: URI
- Конфликт report-uri ценности
- Векторы утечки данных
- Процедура уточнения политики
- Прояснение политики
- Обработка ошибок синтаксического анализа
- Ненормативные соображения на стороне клиента
- Поведение HTTP-сервера
Что дальше?
Окей, мы разобрались с тем что такое CSP, но с чего начать, а самое главное как мы будем тестировать, работает этот механизм или нет?
Я попробую пошагово рассказать свой опыт настройки этого механизма, и что я для этого использовал.
Мое приложение использует стек:
Так же есть прямой доступ к конфигу webpack с конфигурацией сборки.
При локальной разработке у меня используется сервер на node.js "start": "node scripts/start.js"
Но для нашего тестирования CSP нам потребуется настраивать заголовки на сервере, и самым популярным решением является поднять локально сервер на nginx вместо нашего скрипта.
Nginx Шаг 1
В зависимости от вашей os, команды по установке и запуску nginx могут немного отличаться. Так как я использую mac os, я устанавливал nginx через brew ( https://brew.sh
)
После того как мы установили nginx, у нас есть доступ к базовой конфигурации.
Для Mac Os nginx лежит по адресу /usr/local/etc/nginx
либо /opt/homebrew/etc/nginx/nginx.conf
базовый конфиг nginx.conf
Первое что я хотел сделать — чтобы nginx, для начала, просто отдавал мою статику.
Поэтому мой базовый конфиг для локальной работы выглядел так:
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 3000;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
error_page 403 404 500 502 503 504 /index.html;
location = / {
root html;
}
}
include servers/*;
}
В данном случае наш сервер должен отдавать статику по адресу http://localhost:3000/
Теперь можем запустить nginx командой sudo nginx
Если мы перейдем по адресу http://localhost:3000/
то должны увидеть что то вроде этого:

Nginx Шаг 2
Теперь нам нужно сбилдить нашу статику и положить ее в usr/local/var/www
или /opt/homebrew/var/www
В моем проекте это делается командой yarn run build
На выходе получаем папку static
, берем ее внутренности и перемещаем в usr/local/var/www
После этого перезапускаем nginx
командой sudo nginx -s stop && sudo nginx
Теперь на http://localhost:3000
мы должны увидеть наше приложение
P. S Этот шаг нужно повторять каждый раз, когда вы обновляете код своего приложения, если хотите проверить результат
Nginx Шаг 3
Далее будет не лишним настроить протокол https
, чтобы наш сайт открывался по https://localhost:3000
Для этого нам нужно сгенерировать ssl сертификат. В терминале переходим в папку, в которую сгенерируем 2 новых файла. Лично мне удобно открыть WebStorm со своим проектом, и использовать встроенный терминал.
Вводим в терминал команду openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem
После выполнения команды жмем enter чтобы скипать шаги до шага Common name
.
На этом моменте для Common name
вводим 127.0.0.1
чтобы установить сертификат в корневом хранилище сертификатов вашей ОС или в браузере, чтобы он был надежным.
В корне проекта появиться два файла cert.pem key.pem
Далее нам нужно перенести эти два файла в папку /usr/local/etc/ca-certificates
или /opt/homebrew/etc/ca-certificates
И в конфиге nginx добавить следующие поля ниже поля server_name
P. S Опять же путь до файлов может отличаться
ssl_certificate /usr/local/etc/ca-certificates/cert.pem;
ssl_certificate_key /usr/local/etc/ca-certificates/key.pem;
Так же требуется добавить приписку ssl
для поля listen
Получиться примерно так:
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 3000 ssl;
server_name localhost;
ssl_certificate /usr/local/etc/ca-certificates/cert.pem;
ssl_certificate_key /usr/local/etc/ca-certificates/key.pem;
location / {
root html;
index index.html index.htm;
}
error_page 403 404 500 502 503 504 /index.html;
location = / {
root html;
}
}
include servers/*;
}
Перезапускаем nginx командой sudo nginx -s stop && sudo nginx
Теперь сайт должен открываться на https://localhost:3000
Nginx Шаг 4
По идее сейчас все готово для того чтобы внедрять политику CSP.
Для того чтобы ее настраивать нам нужно понимать как она работает.
А работает она с помощью заголовка Content-Security-Policy, мы будем описывать правила, которые браузер должен будет соблюдать, а если какое-либо действие пользователя будет не по правилам — браузер откажется это выполнять.
Настройка начинается с того, что сначала мы запрещаем все, а потом начинаем добавлять исключения.
Начнем с правила script-src
оно определяет допустимые источники JavaScript.
В nginx добавляем заголовок со следующим значением:
add_header Content-Security-Policy "script-src 'self' 'unsafe-inline'";
В коде это выглядит так:
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 3000 ssl;
server_name localhost;
ssl_certificate /usr/local/etc/ca-certificates/cert.pem;
ssl_certificate_key /usr/local/etc/ca-certificates/key.pem;
location / {
add_header Content-Security-Policy "script-src 'self' 'unsafe-inline'";
root html;
index index.html index.htm;
}
error_page 403 404 500 502 503 504 /index.html;
location = / {
root html;
}
}
include servers/*;
}
Перезапускаем nginx командой sudo nginx -s stop && sudo nginx
И перезагружаем наш сайт. Если у вас есть сторонние источники, например метрика, рекапча и тд, то вы увидите ошибку в консоли подобную этой и белую страницу

Это говорит нам о том, что у нас есть источник который не описан в политике csp, и поэтому браузер его не загружает, давайте добавим источник в список разрешенных.
Перезапускаем nginx командой sudo nginx -s stop && sudo nginx
Обновляем страницу и видим что ошибка пропала. По такой же схеме, у вас может быть много ошибок, и далее вам нужно просто внести ресурсы в белый список.
Nginx Шаг 5
Казалось бы мы настроили первое правило script-src
— но нет. В нашем правиле есть ключевое слово 'unsafe-inline'
который означает что мы разрешаем использование всех встроенных скриптов. Использование этого ключевого слова считается небезопасным.
Давайте попробуем удалить это ключевое слово, и посмотреть что будет:

Высокая вероятность того что вы получите такую ошибку выше, и скорее всего подобных ошибок будет +- около 10, а то и больше, в зависимости от того сколько встроенных скриптов вы используете.
Так что же тут случилось, откуда тут вообще взялась эта ошибка?
Так как мы отказались от ключевого слово 'unsafe-inline'
— то мы отказались и от использования встроенных скриптов. Теперь же нам нужно научиться “помечать” какие встроенные скрипты являются безопасными.
Хорошей практикой считается добавлять для наших встроенных скриптов атрибут nonce
, и указывать в нем динамический хеш, тем самым валидируя скрипты. Этот динамический хеш будет генерироваться нашим сервером nginx. Таким образом, если встроенный скрипт не будет иметь хеш, или он будет не совпадать — то nginx откажется его подгружать, тем самым мы себя обезопасим от разных атак.
Настройка хеша, включает в себя изменение кода как со стороны frontend так и со стороны nginx.
Для начала начнем со стороны frontend. В целом, описанные ниже шаги можно применить на большинство фреймворков и библиотек, так как основные настройки делаются в корневом index.js и в webpack конфиге.
Настройка frontend приложения
Сама настройка заключается в том, что мы должны добавить атрибут nonce="CSP_NONCE"
ко всем встроенным скриптам. Само значение CSP_NONCE
на самом деле может быть любым. Это значение нужно для того, чтобы бы в будущем наш nginx находил это значение в статических файлах js, html и заменял на динамический хеш.
Начнем с простого, зайдем в наш index.html файл, и добавим этот атрибут ко всем подключаемым скриптам, стилям и шрифтам. К примеру у меня есть следующие скрипт и шрифт, в которые я добавляю атрибут:
<link nonce="**CSP_NONCE**" rel="preconnect" href="https://fonts.googleapis.com" />
<script nonce="**CSP_NONCE**" type="text/javascript">
window.dataLayer=window.dataLayer||[];
</script>
Добавляем в наш index.html следующую запись
<meta property="csp-nonce" content="**CSP_NONCE**" />
На эту запись могут ориентироваться некоторые UI библиотеки, например Material UI
Так же добавляем в index.html следующий скрипт
<script type="text/javascript" nonce="**CSP_NONCE**">
window.__webpack_nonce__ = "**CSP_NONCE**";
</script>
Тут мы глобально задаем новую переменную webpack_nonce
на которую будут ориентироваться некоторые скрипты и библиотеки.
Как правило в этом месте описаны настройки для различных плагинов eslint, html.
Нам нужно установить плагин html-webpack-inject-attributes-plugin
Подключить его вверху конфига:
И добавить следующую запись последним элементов массива plugins
plugins: [
// ...any plugins
new HtmlWebpackInjectPlugin({
nonce: "**CSP_NONCE**",
}),
]
Так как использую react, у меня есть скрипт scripts/build.js
, который запускается командой yarn run build
, этот файл используется для сборки приложения в production
У вас может быть точно такой же файл, либо какой то другой аналогичный скрипт. В него нужно добавить следующую запись:
process.env.INLINE_RUNTIME_CHUNK = "false";
Открываем наш корневой index.js, и в самый вверх добавляем запись:
// eslint-disable-next-line no-undef
__webpack_nonce__ = window.__webpack_nonce__;
По идее все эти шаги должны привести к тому, что каждый ваш встроенный скрипт, стиль, включая динамические, будет иметь атрибут nonce=» CSP_NONCE
» :
Nginx Шаг 6
После того как мы добавили nonce=» CSP_NONCE
» ко всем встроенным скриптам и стилям, нужно расширить конфигурацию nginx, и добавить:
Два поля в раздел location
:
sub_filter **CSP_NONCE** $request_id;
Поле sub_filter_once
указывает следует ли искать каждую строку для замены один раз.
Поле sub_filter
задает строку для замены и строку замены.
То есть мы находим строку **CSP_NONCE**
в нашей статике, и заменяем ее на значение переменной $request_id
Из заголовка удаляем строку unsafe-inline
которая разрешала нам использование всех встроенных скриптов, и добавляем 'nonce-$request_id'
и 'strict-dynamic'
'strict-dynamic'
— указывает, что доверие, явно предоставляемое скрипту, присутствующему в разметке, путем сопровождения его одноразовым значением или хэшем, должно распространяться на все скрипты, загруженные этим корневым скриптом.
Теперь наш каждый встроенный скрипт будет помечен динамических хешем, тем самым подтверждая что это наш скрипт и его можно загружать.
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 3000 ssl;
server_name localhost;
ssl_certificate /usr/local/etc/ca-certificates/cert.pem;
ssl_certificate_key /usr/local/etc/ca-certificates/key.pem;
location / {
add_header Content-Security-Policy "script-src 'self' 'nonce-$request_id' 'strict-dynamic' https://www.google.com/recaptcha/";
sub_filter_once off;
sub_filter **CSP_NONCE** $request_id;
root html;
index index.html index.htm;
}
error_page 403 404 500 502 503 504 /index.html;
location = / {
root html;
}
}
include servers/*;
}
После перезапуска nginx, у нас должны пропасть ошибки из консоли.
Nginx Шаг 7
Теперь продолжим добавлять различные директивы, например style-src
со значением self
Перезапускаем nginx, обновляем страницу, и смотрим ошибки в консоли.
Скорее всего самые первые ошибки будут указывать на ресурсы, которых нет в white list.
Добавляем ресурс и nonce:
style-src “’self’ ‘nonce-$request_id’ https://*.example.com”
Так же для удобства, выносим каждую директиву в переменную, и получаем:
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 3000 ssl;
server_name localhost;
ssl_certificate /opt/homebrew/etc/ca-certificates/cert.pem;
ssl_certificate_key /opt/homebrew/etc/ca-certificates/key.pem;
set $CSP_SCRIPT_SRC "'self' 'nonce-$request_id' 'strict-dynamic' https://www.google.com/recaptcha/";
set $CSP_STYLE_SRC "'self' 'nonce-$request_id' https://*.example.com";
location / {
add_header Content-Security-Policy "script-src $CSP_SCRIPT_SRC; style-src $CSP_STYLE_SRC";
sub_filter_once off;
sub_filter **CSP_NONCE** $request_id;
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /index.html;
location = / {
root html;
}
}
include servers/*;
}
По такому принципу, вы можете добавить остальные директивы.
У вас получиться примерно так:
#user nobody;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 3000 ssl;
server_name localhost;
# location of ssl certificate
ssl_certificate /usr/local/etc/ca-certificates/cert.pem;
# location of ssl key
ssl_certificate_key /usr/local/etc/ca-certificates/key.pem;
set $CSP_SCRIPT_SRC "'self' 'nonce-$request_id' 'strict-dynamic' https://www.google.com/recaptcha/";
set $CSP_STYLE_SRC "'self' 'nonce-$request_id' https://*.example.com";
set $CSP_CONNECT_SRC "'self' https://*.example.com";
set $CSP_FONT_SRC "'self' https://fonts.gstatic.com/";
set $CSP_IMG_SRC "'self'";
set $CSP_OBJECT_SRC "'self'";
set $CSP_BASE_URI "'self'";
set $CSP_FRAME_SRC "'self' https://www.google.com https://mc.yandex.ru";
set $CSP_MANIFEST_SRC "'self'";
set $CSP_MEDIA_SRC "'self'";
set $CSP_WORKER_SRC "'self'";
set $CSP_FRAME_ANCESTORS "'self'";
location / {
add_header Content-Security-Policy "default-src 'none'; script-src $CSP_SCRIPT_SRC; style-src $CSP_STYLE_SRC; connect-src $CSP_CONNECT_SRC; font-src $CSP_FONT_SRC; img-src $CSP_IMG_SRC; object-src $CSP_OBJECT_SRC; base-uri $CSP_BASE_URI; frame-src $CSP_FRAME_SRC; manifest-src $CSP_MANIFEST_SRC; media-src $CSP_MEDIA_SRC; worker-src $CSP_WORKER_SRC; frame-ancestors $CSP_FRAME_ANCESTORS;";
sub_filter_once off;
sub_filter **CSP_NONCE** $request_id;
root html;
index index.html index.htm;
}
error_page 403 404 500 502 503 504 /index.html;
location = / {
root html;
}
}
include servers/*;
}
В целом на этом моменте можно считать настройку CSP завершенной.
CSP is designed to be fully backward compatible (except CSP version 2 where there are some explicitly-mentioned inconsistencies in backward compatibility; more details here
section 1.1).
Browsers that don’t support it still work with servers that implement it, and vice versa: browsers that don’t support CSP ignore it, functioning as usual, defaulting to the standard same-origin policy for web content.
If the site doesn’t offer the CSP header, browsers likewise use the standard same-origin policy
.
To enable CSP, you need to configure your web server to return the Content-Security-Policy
HTTP header.
(Sometimes you may see mentions of the X-Content-Security-Policy
header, but that’s an older version and you don’t need to specify it anymore.)
Alternatively, the <meta>
element can be used to configure a policy, for example:
default-src 'self'; img-src https://*; child-src 'none';
Note:
Some features, such as sending CSP violation reports, are only available when using the HTTP headers.
Threats
Mitigating cross-site scripting
A primary goal of CSP is to mitigate and report XSS attacks. Атаки X SS используют доверие браузера к содержимому, полученному с сервера.
Вредоносные сценарии выполняются браузером жертвы, поскольку браузер доверяет источнику контента, даже если он исходит не оттуда, откуда кажется.
В качестве окончательной формы защиты сайты, которые не хотят допускать использование скриптов,
Executed может глобально запретить выполнение скрипта.
Смягчение атак с перехватом пакетов
Помимо ограничения доменов, из которых можно загружать контент, сервер может указать, какие протоколы разрешено использовать;
например (в идеале с точки зрения безопасности) сервер может указать, что весь контент должен загружаться с использованием HTTPS.
Полная стратегия безопасности передачи данных включает не только применение HTTPS для передачи данных, но и пометку всех файлов cookie с помощью secure
атрибут
и обеспечение автоматического перенаправления со страниц HTTP на их аналоги HTTPS.
Сайты также могут использовать Strict-Transport-Security
HTTP-заголовок, гарантирующий, что браузеры подключаются к ним только по зашифрованному каналу.
Использование CSP
Указание вашей политики
Вы можете использовать Content-Security-Policy
HTTP-заголовок для указания вашей политики, например:
Политика представляет собой строку, содержащую директивы политики, описывающие вашу Политику безопасности контента.
Написание политики
Полный список директив политики см. на справочной странице заголовка Content-Security-Policy .
.
Примеры: общие случаи использования
В этом разделе приведены примеры некоторых общих сценариев политики безопасности.
Пример 1
Пример 2
default-src 'self' example.com *.example.com
Пример 3
- Изображения могут загружаться откуда угодно (обратите внимание на подстановочный знак «*»).
- Медиафайлы разрешены только с сайтов example.org и example.net (а не с поддоменов этих сайтов).
- Исполняемый сценарий разрешен только с сайта userscripts.example.com.
Пример 4
Сервер разрешает доступ только к документам, загружаемым специально по HTTPS через единый источник onlinebanking.example.com.




