В условиях все более взаимосвязанного мира обеспечение безопасности веб-приложений становится критически важной для защиты пользователей и их данных.
Серия статей «Веб-безопасность» охватывает фундаментальные концепции и методы обеспечения безопасности, которые должен знать и внедрять каждый разработчик.
Что такое CSP?
CSP (content security policy — стратегия защиты контента) — стандарт безопасности, который помогает предотвращать XSS (cross-site scripting — межсайтовый скриптинг) и другие инъекционные атаки, контролируя ресурсы, которые разрешено загружать веб-странице.
Чтобы лучше понять CSP, разберемся сначала с межсайтовым скриптингом (XSS), поскольку методы CSP в первую очередь предназначены для борьбы с уязвимостями такого типа.
Межсайтовый скриптинг (XSS)
Что такое XSS?
XSS-атаки возникают, когда злоумышленник внедряет на сайт вредоносные скрипты, которые затем выполняются в браузере жертвы.
Методы предотвращения
- Escape-вывод: использование библиотек вроде DOMPurify для очистки пользовательского ввода.
- Использование файлов cookies только по HTTP: предотвращение доступа злоумышленников к cookies через JavaScript.
- Следование строгим требованиям CSP: уменьшение влияния внедренных скриптов.
- Валидация и очистка ввода: рассмотрение всех вводимых пользователем данных как данных с нулевым уровнем доверия.
Принцип работы CSP
CSP позволяет разработчикам определить «белый список» доверенных источников контента, таких как скрипты, стили, изображения и многое другое. Таким образом, браузер применяет строгие правила загрузки, снижая риск выполнения вредоносного кода.
CSP состоит из серии инструкций, передаваемых веб-сайтом браузеру, которые предписывают браузеру наложить ограничения на действия, которые разрешено выполнять коду, содержащемуся на сайте.
Пример
Простой CSP-заголовок может выглядеть следующим образом;
Content-Security-Policy: script-src 'self' https://trustedsource.com;
Такая стратегия гарантирует загрузку скриптов только из того же источника или из указанного доверенного источника.
Основная цель CSP — контролировать, какие ресурсы, особенно JavaScript, разрешено загружать на веб-страницу. Это служит ключевым механизмом защиты от атак межсайтового скриптинга (XSS), когда злоумышленники внедряют на сайт вредоносный код.
Помимо защиты от XSS, CSP также помогает предотвратить кликджекинг и обеспечивает безопасную загрузку страниц сайта по протоколу HTTPS.
CSP-директивы
CSP-директивы используются для указания типов контента и их разрешенных источников. К наиболее распространенным директивам относятся:
default-src: определяет механизм получения ресурсов по умолчанию;
script-src: определяет разрешенные источники для JavaScript;
style-src: определяет разрешенные источники для CSS-стилей;
img-src: определяет механизм получения изображений;
connect-src: определяет URL-адреса, которые могут использоваться для AJAX, WebSocket и других запросов к ресурсам.
Одна из специальных директив получения данных — default-src — устанавливает механизм возврата для всех ресурсов, чьи директивы не указаны явно.
Примеры CSP-инструкций
1. Базовая инструкция, позволяющая использовать скрипты и стили одного происхождения:
Content-Security-Policy: default-src 'self';
2. Инструкция, разрешающая использовать скрипты только из доверенных CDN (content delivery network — сеть доставки контента) и требующая блокировать встроенные скрипты:
Content-Security-Policy: script-src 'self' https://trustedcdn.com;
3. Инструкция, запрещающая встраивание стилей и ограничивающая загрузку шрифтов на определенные домены:
Content-Security-Policy: style-src 'self'; font-src https://trustedfonts.com;
Контроль над загрузкой ресурсов
XSS и загрузка ресурсов
CSP можно использовать для контроля ресурсов, которые разрешено загружать документу. В основном это используется для защиты от атак межсайтового скриптинга (XSS).
Вредоносный код, внедряемый в документ, может принимать различные формы, в том числе:
- тег <script>, содержащий ссылку на вредоносный источник:
<script src="https://evil.example.com/hacker.js"></script>
- тег
<script>, содержащий встроенный JavaScript:
<script>
console.log("You've been hacked!");
</script>
- встроенный обработчик событий:
<img onmouseover="console.log(`You've been hacked!`)" />
-
javascript:— URL:
<iframe src="javascript:console.log(`You've been hacked!`)"></iframe>
- строковый аргумент для небезопасного API, например eval():
eval("console.log(`You've been hacked!`)");
CSP защитит от всего этого. С помощью CSP можно:
- определить разрешенные источники для файлов JavaScript и других ресурсов, эффективно блокируя загрузку с сайта https://evil.example.com;
- блокировать встроенные теги скриптов;
- разрешить только теги скриптов, которые имеют правильный набор nonce или hash;
- блокировать встроенные обработчики событий;
- блокировать
javascript:— URL;
- блокировать опасные API, например
eval().
Блокирование ресурсов
Чтобы полностью заблокировать тип ресурса, используется ключевое слово 'none'. Например, следующая директива блокирует все ресурсы <object> и <embed>:
Content-Security-Policy: object-src 'none'
Ключевое слово 'none' — специальное значение в CSP, которое явно запрещает загрузку всех ресурсов по директиве. Оно уникально тем, что не может сочетаться с другими исходными выражениями в рамках одной директивы. Если указано 'none', все остальные исходные выражения в этой директиве игнорируются.
Nonce
Nonce (сокращение от «numbers used once» — числа, используемые один раз) — мощная функция CSP, используемая для разрешения встроенных скриптов и стилей при сохранении надежной безопасности.
Она работает путем динамической генерации уникального случайного значения при каждой загрузке страницы, которое затем прикрепляется к элементам скриптов или стилей в качестве атрибута nonce. Только элементы с правильным значением nonce выполняются или применяются браузером.
Как функционирует nonce
Определение nonce в CSP: случайное значение генерируется для каждого HTTP-ответа и включается в заголовок Content-Security-Policy.
Пример:
Content-Security-Policy: script-src 'nonce-abc123';
Прикрепление nonce к встроенным скриптам: добавление того же значения nonce в атрибут nonce встроенных элементов <script>.
<script nonce="abc123">
console.log('This script will execute.');
</script>
Исполнение браузером: браузер проверяет, совпадает ли nonce в заголовке CSP с nonce в элементе скрипта; если совпадают, скрипт выполняется; в противном случае скрипт блокируется.
Пример CSP- заголовка с использованием nonce
CSP-заголовок с использованием nonce может выглядеть следующим образом:
Content-Security-Policy: script-src 'self' 'nonce-randomValue123';
Это позволяет:
- использовать скрипты одного происхождения (
'self');
- использовать встроенные скрипты с соответствующим значением nonce (
'nonce-randomValue123').
Преимущества nonce
- Надежная защита от XSS: nonce предотвратит несанкционированное встраивание скриптов, даже если злоумышленник внедрит их на страницу.
- Простота использования: разработчики могут безопасно включать необходимые встроенные скрипты или стили, не переписывая свой код.
- Динамическая гибкость: nonce может генерироваться уникальным образом для каждой загружаемой страницы, что снижает риск повторного использования или подделки.
Hash
Hash — еще один метод в CSP, который позволяет использовать определенные встроенные скрипты или стили, сохраняя при этом безопасность. Вместо того чтобы разрешать все встроенные скрипты, hash разрешает только те, контент которых соответствует заранее определенному hash-значению.
Это гарантирует, что будет выполняться только разрешенный встроенный код, даже если злоумышленникам удастся внедрить вредоносные скрипты.
Зачем использовать hash?
Hash обеспечивает дополнительный уровень безопасности для встроенного контента, поскольку:
- разрешает выполнение только явно авторизованных встроенных скриптов или стилей;
- предотвращает выполнение вредоносного кода.
Как функционирует hash
- Вычисление hash: вычисляется криптографический hash (SHA-256, SHA-384 или SHA-512) контента встроенного скрипта или стиля.
- Включение hash в CSP: значение hash добавляется в заголовок
Content-Security-Policy.
- Проверка браузером: браузер вычисляет hash каждого встроенного элемента и сравнивает его с hash в CSP. Если они совпадают, контент выполняется или применяется; в противном случае — блокируется.
Пример CSP с hash
CSP-заголовок, разрешающий определенный встроенный скрипт с помощью hash SHA-256:
Content-Security-Policy: script-src 'self' 'sha256-abc123...xyz456';
Здесь:
'self'разрешает выполнение скриптов того же происхождения;
'sha256-abc123...xyz456'разрешает выполнение встроенных скриптов, соответствующих этому hash.
Преимущества hash
- Надежная авторизация: метод hash разрешает только определенные встроенные скрипты или стили, которые явно перечислены в CSP.
- Повышенная безопасность: даже если злоумышленники внедрят свой скрипт, он не будет соответствовать разрешенному hash и не выполнится.
- Простота обслуживания: метод hash обеспечивает безопасность встроенных скриптов, не полагаясь на динамические элементы, такие как nonce.
Метод hash проверяет корректность контента встроенного элемента. Метод nonce динамически идентифицирует доверенные встроенные скрипты или стили.
const hash1 = "sha256-ex2O7MWOzfczthhKm6azheryNVoERSFrPrdvxRtP8DI=";
const hash2 = "sha256-H/eahVJiG1zBXPQyXX0V6oaxkfiBdmanvfG9eZWSuEc=";
const csp = `script-src '${hash1}' '${hash2}'`;
const content = `
<script src="./main.js" integrity="${hash2}"></script>
<script>console.log("hello!");</script>
<h1>Hello world</h1>
`;
app.get("/", (req, res) => {
res.setHeader("Content-Security-Policy", csp);
res.send(content);
});
Инструкции на основе схем
Fetch-директивы могут включать схему и разрешать обслуживание ресурсов по этой схеме. Эта, например, позволяет требовать HTTPS для всех загружаемых ресурсов.
Content-Security-Policy: default-src https:
Инструкции на основе местоположения
Fetch-директивы могут управлять загрузкой ресурсов в зависимости от того, где они расположены.
Ключевое слово 'self' позволяет использовать ресурсы, которые имеют одинаковое происхождение с самим документом.
Content-Security-Policy: img-src 'self'
В CSP можно указать одно или несколько имен хостов (включая возможность использования символов подстановки) в качестве доверенных источников для загрузки ресурсов.
Такой подход гарантирует разрешение только контента, обслуживаемого с указанных хостов, что повышает безопасность и обеспечивает гибкость.
Content-Security-Policy: img-src *.example.org
//или
Content-Security-Policy: img-src 'self' *.example.org example.com
Встроенный JavaScript
Когда CSP включает директиву default-src или script-src, встроенный JavaScript (например, теги <script> с атрибутами code или onclick) по умолчанию блокируется.
Это ограничение повышает безопасность, предотвращая выполнение потенциально вредоносных встроенных скриптов, которые являются распространенным вектором XSS-атак.
<script>
console.log("Hello from an inline script");
</script>
<img src="x" onerror="console.log('Hello from an inline event handler')" />
<a href="javascript:console.log('Hello from a javascript: URL')"></a>
Ключевое слово unsafe-inline в CSP можно использовать для отмены ограничений по встроенному JavaScript. При его использовании встроенные скрипты могут выполняться, невзирая на риски безопасности. Такое выполнение полезно в некоторых ситуациях, но по возможности этого следует избегать из-за риска возникновения XSS-уязвимостей.
eval() и аналогичные API
Если CSP содержит директиву default-src или script-src, использование eval() и аналогичных API JavaScript запрещено по умолчанию.
Эти API запрещены, поскольку позволяют выполнять динамический код, что создает значительные риски безопасности, в частности, связанные с XSS и другими инъекционными атаками.
eval('console.log("hello from eval()")');
//Конструктор Function()
const sum = new Function("a", "b", "return a + b");
//Строковый аргумент для setTimeout() и setInterval():
setTimeout("console.log('hello from setTimeout')", 1);
Ключевое слово unsafe-eval можно использовать для переопределения этого поведения.
Строгий режим CSP
Строгий режим CSP (Strict CSP) — надежная конфигурация безопасности, разработанная для минимизации риска XSS-атак и других угроз, имеющих отношение к контенту. Она накладывает жесткие ограничения на то, как и откуда веб-страница может загружать и выполнять ресурсы, обеспечивая надежную защиту.
Ключевые характеристики строгого режима CSP
- Никаких unsafe-inline-скриптов и стилей. Встроенные скрипты и стили запрещены, если они явно не авторизованы с помощью (
nonce-) или hash (sha256-,sha384-,sha512-). Это гарантирует выполнение только доверенного встроенного контента.
- Никаких unsafe-eval. API, такие как
eval()и конструкторыFunction, отключаются, что предотвращает выполнение динамически конструируемого кода.
- Явный «белый список» ресурсов. Ресурсы, такие как скрипты, стили, изображения и шрифты, должны поступать из явно определенных доверенных источников.
- Блокировка смешанного контента. Обеспечивает передачу всего контента по протоколу HTTPS, запрещая появление небезопасных (HTTP) ресурсов на странице.
Пример строгого режима CSP
Типичный строгий режим CSP может выглядеть следующим образом:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://trusted-cdn.com 'nonce-randomValue123';
style-src 'self' https://trusted-styles.com 'sha256-x3xM...';
img-src 'self' https://images.example.com;
font-src https://fonts.example.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
object-src 'none';
base-uri 'self';
Инструкции строгого CSP
1. default-src 'self': позволяет загружать все ресурсы только из одного источника ('self').
2. script-src:
- разрешает скрипты только из одного источника (
'self') или доверенной CDN;
- позволяет авторизовать встроенные скрипты только в том случае, если указан действительный nonce (
nonce-randomValue123) или hash.
3. style-src:
- разрешает загрузку стилей, если они из одного и того же источника или если это доверенные CDN-стили;
- разрешает встроенные стили, если они соответствуют определенному hash (
sha256-x3xM...).
4. img-src: требует, чтобы изображения были только из одного источника или чтобы это было определенное CDN-изображение.
5. font-src: требует, чтобы шрифты были получены из определенного надежного источника.
6. connect-src: ограничивает сетевые запросы (например, AJAX, WebSockets) одним и тем же источником или надежным API-сервером.
7. frame-ancestors 'none': предотвращает встраивание страницы во фреймы (frames) или встроенные фреймы (iframes), защищая от атак кликджекинга.
8. object-src 'none': запрещает использование элементов <object>, <embed> и <applet>, снижая риски атаки.
9. base-uri 'self': гарантирует, что тег <base>, если он используется, может указывать только на тот же источник.
Строгий режим CSP на основе nonce выглядит следующим образом:
Content-Security-Policy:
script-src 'nonce-{RANDOM}';
object-src 'none';
base-uri 'none';
Строгий CSP на основе hash выглядит так же, только вместо nonce используется hash:
Content-Security-Policy:
script-src 'sha256-{HASHED_SCRIPT}';
object-src 'none';
base-uri 'none';
Ключевое слово strict-dynamic
Строгий режим CSP может быть сложным для реализации, если веб-приложение полагается на скрипты, которые вы не контролируете, например сторонние библиотеки или сервисы. Причинами возникновения сложностей могут быть:
- Динамическая загрузка скриптов: сторонние скрипты часто загружают дополнительные ресурсы динамически.
- Встроенные скрипты: эти скрипты могут использовать встроенный JavaScript, что обычно запрещено в строгом режиме CSP.
Сторонние скрипты, не предназначенные для распространения методов nonce или hash, не соответствуют требованиям строгого CSP, что приводит к блокированию функциональности.
Чтобы устранить это ограничение, вводится ключевое слово strict-dynamic. Эта директива CSP позволяет скрипту, проверенному nonce или hash, загружать дополнительные скрипты, даже если эти скрипты не имеют собственных nonce или hash.
Этот механизм распространяет доверие начального скрипта на скрипты, которые он динамически загружает, создавая каскад проверенных ресурсов.
Пример использования
Рассмотрим скрипт, в котором веб-приложение использует стороннюю библиотеку, размещенную в CDN. Без strict-dynamic пришлось бы перечислять все потенциальные домены и ресурсы, динамически загружаемые этой библиотекой. С strict-dynamic CSP становится проще:
Content-Security-Policy: script-src 'nonce-abc123' 'strict-dynamic' https:;
'nonce-abc123': начальный скрипт явно проверен;
'strict-dynamic': дополнительные скрипты, загруженные начальным скриптом, также являются проверенными;
https:: все динамически загружаемые скрипты ограничены безопасным HTTPS-источником.
Такая настройка обеспечивает безопасную и удобную загрузку скриптов без явного перечисления всех потенциальных доменов.
Защита от кликджекинга
Кликджекинг (clickjacking) — тип атаки, при которой злоумышленник обманывает пользователей, заставляя их взаимодействовать с элементом на веб-странице (например, нажимать кнопку или ссылку), накладывая на него прозрачный или непрозрачный слой, маскирующий его истинное назначение.
Это может привести к выполнению несанкционированных действий без согласия пользователя, таких как отправка форм, изменение настроек или инициирование транзакций.
Злоумышленник помещает легитимную веб-страницу или определенный элемент (например, кнопку) в <iframe> и накладывает на него обманчивый контент. Когда пользователь взаимодействует с обманчивым контентом, он неосознанно вызывает действие на легитимной странице.
Директива frame-ancestors в CSP определяет источники, которым разрешено встраивать сайт во фрейм. Она более гибкая и современная, чем X-Frame-Options.
Content-Security-Policy: frame-ancestors 'self';
Тестирование CSP
В режиме «только для отчетов» (report-only) CSP не соблюдается, то есть любые нарушения, связанные с ресурсами, регистрируются, но не блокируются. Нарушения отправляются на конечную точку отчетности, которую вы укажете в своем CSP.
Это позволит оценить, как CSP влияет на функциональность сайта, не нарушая при этом работу пользователей.
Как использовать CSP-режим «только для отчетов»
Чтобы использовать режим «только для отчетов», установите заголовок Content-Security-Policy-Report-Only в HTTP-ответе.
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' https://trustedcdn.com; report-uri /csp-report-endpoint;
// Конечная точка отчета может быть задана таким же образом или в самой директиве CSP
Reporting-Endpoints: csp-endpoint="https://example.com/csp-report-endpoint"
Поведение нескольких CSP-стратегий
Если в одном ответе присутствуют и заголовок Content-Security-Policy-Report-Only, и заголовок Content-Security-Policy, включаются обе стратегии:
- заголовок
Content-Security-Policyобеспечивает соблюдение CSP-стратегии и блокирует нарушения;
- заголовок
Content-Security-Policy-Report-Onlyгенерирует отчеты о нарушениях, не блокируя контента.
Такой двойной подход позволяет экспериментировать с изменениями CSP-стратегий, прежде чем вводить их в действие.
Реализация CSP в Express.js/Nodejs
Для реализации CSP в приложении Express.js можно использовать промежуточное ПО helmet, которое упрощает управление HTTP-заголовками, связанными с безопасностью, включая CSP.
const express = require('express');
const helmet = require('helmet');
const app = express();
// Определите свою CSP-стратегию
const cspConfig = {
useDefaults: true, // Use default helmet CSP directives
directives: {
// Примеры пользовательских директив
"default-src": ["'self'"], // Разрешить ресурсы одинакового происхождения
"script-src": ["'self'", "https://trusted-cdn.com"], // Разрешить встроенные скрипты и доверенные CDN
"style-src": ["'self'", "https://trusted-cdn.com"], // Разрешить стили из одного источника или доверенной CDN
"img-src": ["'self'", "data:"], // Разрешить изображения одинакового происхождения и URI данных
"connect-src": ["'self'", "https://api.example.com"], // Разрешить подключение к определенным API
"frame-src": ["'none'"], // Запретить iframe любого происхождения
},
reportOnly: false, // Установите значение true, если хотите сообщать только о нарушениях
};
// Используйте Helmet для настройки CSP
app.use(helmet.contentSecurityPolicy(cspConfig));
app.get('/', (req, res) => {
res.send('CSP is configured!');
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Использование CSP-методов позволяет защитить приложения от уязвимостей, защитить данные и повысить доверие пользователей.
Читайте также:
- Введение в Веб-безопасность
- Encore.ts — в 9 раз быстрее Express.js и в 3 раза быстрее Bun + Zod
- Знакомство с SurrealDB с помощью Express.js, Node.js и TypeScript
Читайте нас в Telegram, VK и Дзен
Перевод статьи Satish Kumar: Web Security: Content Security Policy





