Межсайтовый скриптинг (XSS) , возможность внедрения вредоносных скриптов в веб-приложение, уже более десяти лет является одной из крупнейших уязвимостей веб-безопасности.
Политика безопасности контента (CSP) — это дополнительный уровень безопасности, который помогает снизить риск XSS. Чтобы настроить CSP, добавьте HTTP-заголовок Content-Security-Policy
на веб-страницу и установите значения, которые контролируют, какие ресурсы пользовательский агент может загружать для этой страницы.
На этой странице объясняется, как использовать CSP на основе одноразовых номеров или хэшей для уменьшения XSS вместо широко используемых CSP на основе белого списка хостов, которые часто оставляют страницу открытой для XSS, поскольку их можно обойти в большинстве конфигураций .
Ключевой термин: одноразовый номер — это случайное число, используемое только один раз, которое вы можете использовать, чтобы пометить тег <script>
как доверенный.
Ключевой термин: хеш-функция — это математическая функция, которая преобразует входное значение в сжатое числовое значение, называемое хешем . Вы можете использовать хэш (например, SHA-256 ), чтобы пометить встроенный тег <script>
как доверенный.
Политику безопасности контента, основанную на одноразовых значениях или хэшах, часто называют строгим CSP . Когда приложение использует строгий CSP, злоумышленники, обнаружившие недостатки внедрения HTML, обычно не могут использовать их, чтобы заставить браузер выполнить вредоносные сценарии в уязвимом документе. Это связано с тем, что строгий CSP допускает только хешированные сценарии или сценарии с правильным значением nonce, сгенерированным на сервере, поэтому злоумышленники не могут выполнить сценарий, не зная правильного значения nonce для данного ответа.
Почему вам следует использовать строгий CSP?
Если на вашем сайте уже есть CSP, который выглядит как script-src www.googleapis.com
, он, вероятно, неэффективен против межсайтового взаимодействия. Этот тип CSP называется CSP белого списка . Они требуют тщательной настройки и могут быть обойдены злоумышленниками.
Строгие CSP, основанные на криптографических одноразовых кодах или хэшах, позволяют избежать этих ошибок.
Строгая структура CSP
Базовая строгая политика безопасности контента использует один из следующих заголовков ответа HTTP:
Строгий CSP на основе nonce
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
Строгий CSP на основе хэша
Content-Security-Policy:
script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
Следующие свойства делают такого CSP «строгим» и, следовательно, безопасным:
- Он использует одноразовые номера
'nonce-{RANDOM}'
или хэши'sha256-{HASHED_INLINE_SCRIPT}'
чтобы указать, какие теги<script>
доверяет разработчику сайта выполняться в браузере пользователя. - Он устанавливает
'strict-dynamic'
чтобы уменьшить усилия по развертыванию CSP на основе одноразового номера или хэша, автоматически разрешая выполнение сценариев, созданных доверенным сценарием. Это также разблокирует использование большинства сторонних библиотек и виджетов JavaScript. - Он не основан на списках разрешенных URL-адресов, поэтому не подвержен обычным обходам CSP .
- Он блокирует ненадежные встроенные сценарии, такие как встроенные обработчики событий или
javascript:
URI. - Он ограничивает
object-src
отключением опасных плагинов, таких как Flash. - Он ограничивает
base-uri
, чтобы блокировать внедрение тегов<base>
. Это не позволяет злоумышленникам изменить расположение скриптов, загруженных с относительных URL-адресов.
Примите строгий CSP
Чтобы принять строгий CSP, вам необходимо:
- Решите, должно ли ваше приложение устанавливать CSP на основе одноразового номера или хэша.
- Скопируйте CSP из раздела структуры Strict CSP и установите его в качестве заголовка ответа во всем приложении.
- Выполните рефакторинг HTML-шаблонов и клиентского кода для удаления шаблонов, несовместимых с CSP.
- Разверните свой CSP.
Вы можете использовать аудит Lighthouse (v7.3.0 и выше с флагом --preset=experimental
) на протяжении всего этого процесса, чтобы проверить, есть ли на вашем сайте CSP и достаточно ли он строг, чтобы быть эффективным против XSS.
Шаг 1. Решите, нужен ли вам CSP на основе одноразового номера или хэша.
Вот как работают два типа строгого CSP:
CSP на основе Nonce
Используя CSP на основе nonce, вы генерируете случайное число во время выполнения , включаете его в свой CSP и связываете его с каждым тегом сценария на вашей странице. Злоумышленник не сможет включить или запустить вредоносный сценарий на вашей странице, поскольку ему необходимо будет угадать правильное случайное число для этого сценария. Это работает только в том случае, если число невозможно угадать и оно генерируется заново во время выполнения для каждого ответа.
Используйте CSP на основе nonce для HTML-страниц, отображаемых на сервере. Для этих страниц вы можете создать новое случайное число для каждого ответа.
CSP на основе хэша
Для CSP на основе хэша хэш каждого встроенного тега сценария добавляется в CSP. Каждый скрипт имеет свой хэш. Злоумышленник не сможет включить или запустить вредоносный сценарий на вашей странице, поскольку для его запуска хэш этого сценария должен находиться в вашем CSP.
Используйте CSP на основе хэша для HTML-страниц, обслуживаемых статически, или страниц, которые необходимо кэшировать. Например, вы можете использовать CSP на основе хэша для одностраничных веб-приложений, созданных с помощью таких платформ, как Angular, React или других, которые статически обслуживаются без рендеринга на стороне сервера.
Шаг 2. Установите строгий CSP и подготовьте сценарии.
При настройке CSP у вас есть несколько вариантов:
- Режим «только отчет» (
Content-Security-Policy-Report-Only
) или режим принудительного применения (Content-Security-Policy
). В режиме только отчетов CSP пока не блокирует ресурсы, поэтому на вашем сайте ничего не ломается, но вы можете видеть ошибки и получать отчеты обо всем, что могло быть заблокировано. Локально, когда вы настраиваете CSP, это не имеет особого значения, поскольку в обоих режимах ошибки отображаются в консоли браузера. Во всяком случае, режим принудительного применения может помочь вам найти ресурсы, которые блокирует ваш проект CSP, поскольку блокировка ресурса может сделать вашу страницу неработающей. Режим «только отчет» становится наиболее полезным на более позднем этапе процесса (см. шаг 5 ). - Заголовок или HTML-тег
<meta>
. Для локальной разработки тег<meta>
может быть более удобным для настройки вашего CSP и быстрого просмотра того, как он влияет на ваш сайт. Однако:- Позже, при развертывании вашего CSP в рабочей среде, мы рекомендуем установить его как заголовок HTTP.
- Если вы хотите настроить CSP в режиме «только отчет», вам необходимо установить его в качестве заголовка, поскольку метатеги CSP не поддерживают режим «только отчет».
Установите следующий заголовок HTTP-ответа Content-Security-Policy
в своем приложении:
Content-Security-Policy: script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'; base-uri 'none';
Создать одноразовый номер для CSP
Nonce — это случайное число, используемое только один раз за загрузку страницы. CSP на основе nonce может смягчить XSS только в том случае, если злоумышленники не могут угадать значение nonce. Одноразовый номер CSP должен быть:
- Криптографически стойкое случайное значение (в идеале длиной более 128 бит).
- Создается заново для каждого ответа
- Кодировка Base64
Вот несколько примеров того, как добавить одноразовый номер CSP в серверные платформы:
- Джанго (питон)
- Экспресс (JavaScript):
const app = express(); app.get('/', function(request, response) { // Generate a new random nonce value for every response. const nonce = crypto.randomBytes(16).toString("base64"); // Set the strict nonce-based CSP response header const csp = `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`; response.set("Content-Security-Policy", csp); // Every <script> tag in your application should set the `nonce` attribute to this value. response.render(template, { nonce: nonce }); });
Добавьте атрибут nonce
к элементам <script>
При использовании CSP на основе nonce каждый элемент <script>
должен иметь атрибут nonce
, соответствующий случайному значению nonce, указанному в заголовке CSP. Все скрипты могут иметь один и тот же nonce. Первым шагом является добавление этих атрибутов во все сценарии, чтобы CSP разрешал их использование.
Установите следующий заголовок HTTP-ответа Content-Security-Policy
в своем приложении:
Content-Security-Policy: script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic'; object-src 'none'; base-uri 'none';
Для нескольких встроенных скриптов синтаксис следующий: 'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'
.
Загружать исходные скрипты динамически
Вы можете динамически загружать сторонние скрипты, используя встроенный скрипт.
<script> var scripts = [ 'https://example.org/foo.js', 'https://example.org/bar.js']; scripts.forEach(function(scriptUrl) { var s = document.createElement('script'); s.src = scriptUrl; s.async = false; // to preserve execution order document.head.appendChild(s); }); </script>
<script src="https://example.org/foo.js"></script> <script src="https://example.org/bar.js"></script>
Рекомендации по загрузке скрипта
В примере встроенного скрипта добавлено s.async = false
, чтобы гарантировать, что foo
выполняется до bar
, даже если bar
загружается первым. В этом фрагменте s.async = false
не блокирует синтаксический анализатор во время загрузки сценариев, поскольку сценарии добавляются динамически. Анализатор останавливается только во время выполнения сценариев, как и в случае async
сценариев. Однако, используя этот фрагмент, имейте в виду:
- Один или оба сценария могут выполниться до завершения загрузки документа. Если вы хотите, чтобы документ был готов к моменту выполнения сценариев, дождитесь события
DOMContentLoaded
прежде чем добавлять сценарии. Если это вызывает проблемы с производительностью из-за того, что скрипты не начинают загружаться достаточно рано, используйте теги предварительной загрузки ранее на странице. -
defer = true
ничего не делает. Если вам нужно такое поведение, запустите сценарий вручную, когда это необходимо.
Шаг 3. Рефакторинг HTML-шаблонов и клиентского кода.
Для запуска сценариев можно использовать встроенные обработчики событий (например, onclick="…"
, onerror="…"
) и URI JavaScript ( <a href="javascript:…">
). Это означает, что злоумышленник, обнаруживший ошибку XSS, может внедрить этот тип HTML и выполнить вредоносный JavaScript. CSP на основе одноразового номера или хэша запрещает использование такого типа разметки. Если на вашем сайте используется какой-либо из этих шаблонов, вам придется преобразовать их в более безопасные альтернативы.
Если вы включили CSP на предыдущем шаге, вы сможете видеть нарушения CSP в консоли каждый раз, когда CSP блокирует несовместимый шаблон.
В большинстве случаев исправить это просто:
Рефакторинг встроенных обработчиков событий
<span id="things">A thing.</span> <script nonce="${nonce}"> document.getElementById('things').addEventListener('click', doThings); </script>
<span onclick="doThings();">A thing.</span>
Рефакторинг javascript:
URI
<a id="foo">foo</a> <script nonce="${nonce}"> document.getElementById('foo').addEventListener('click', linkClicked); </script>
<a href="javascript:linkClicked()">foo</a>
Удалите eval()
из вашего JavaScript.
Если ваше приложение использует eval()
для преобразования сериализации строк JSON в объекты JS, вам следует провести рефакторинг таких экземпляров в JSON.parse()
, что также быстрее .
Если вы не можете удалить все случаи использования eval()
, вы все равно можете установить строгий CSP на основе nonce, но вам придется использовать ключевое слово CSP 'unsafe-eval'
, что делает вашу политику немного менее безопасной.
Вы можете найти эти и другие примеры такого рефакторинга в этой строгой кодовой лаборатории CSP:
Шаг 4 (необязательно). Добавьте резервные варианты для поддержки старых версий браузера.
Если вам нужна поддержка старых версий браузера:
- Использование
strict-dynamic
требует добавленияhttps:
в качестве запасного варианта для более ранних версий Safari. Когда вы это сделаете:- Все браузеры, поддерживающие
strict-dynamic
игнорируют резервный вариантhttps:
поэтому это не уменьшит силу политики. - В старых браузерах сценарии извне могут загружаться только в том случае, если они исходят из HTTPS-источника. Это менее безопасно, чем строгий CSP, но все же предотвращает некоторые распространенные причины XSS, такие как внедрение
javascript:
URI.
- Все браузеры, поддерживающие
- Чтобы обеспечить совместимость с очень старыми версиями браузеров (4+ лет), вы можете добавить
unsafe-inline
в качестве запасного варианта. Все последние браузеры игнорируютunsafe-inline
если присутствует nonce или хэш CSP.
Content-Security-Policy:
script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';
Шаг 5. Разверните CSP
Убедившись, что ваш CSP не блокирует никакие допустимые сценарии в вашей локальной среде разработки, вы можете развернуть CSP в промежуточной, а затем в производственной среде:
- (Необязательно) Разверните CSP в режиме «только отчет», используя заголовок
Content-Security-Policy-Report-Only
. Режим «Только отчеты» удобен для тестирования потенциально критических изменений, таких как новый CSP в рабочей среде, прежде чем вы начнете применять ограничения CSP. В режиме только отчетов ваш CSP не влияет на поведение вашего приложения, но браузер по-прежнему генерирует консольные отчеты об ошибках и нарушениях, когда обнаруживает шаблоны, несовместимые с вашим CSP, поэтому вы можете увидеть, что могло бы сломаться для ваших конечных пользователей. Дополнительную информацию см. в разделе Reporting API . - Если вы уверены, что ваш CSP не нарушит работу вашего сайта для конечных пользователей, разверните свой CSP, используя заголовок ответа
Content-Security-Policy
. Мы рекомендуем настроить CSP с использованием HTTP-заголовка на стороне сервера, поскольку он более безопасен, чем тег<meta>
. После выполнения этого шага ваш CSP начнет защищать ваше приложение от XSS.
Ограничения
Строгий CSP обычно обеспечивает надежный дополнительный уровень безопасности, который помогает снизить риск XSS. В большинстве случаев CSP значительно уменьшает поверхность атаки, отвергая опасные шаблоны, такие как javascript:
URI. Однако в зависимости от типа CSP, который вы используете (nonce, хэши, со 'strict-dynamic'
или без него), бывают случаи, когда CSP также не защищает ваше приложение:
- Если вы используете сценарий nonce, но есть внедрение непосредственно в тело или параметр
src
этого элемента<script>
. - При наличии внедрений в местоположения динамически создаваемых скриптов (
document.createElement('script')
), в том числе в любые библиотечные функции, создающие узлы DOMscript
на основе значений их аргументов. Сюда входят некоторые распространенные API, такие как.html()
в jQuery, а также.get()
и.post()
в jQuery < 3.0. - Если в старых приложениях AngularJS есть внедрение шаблонов. Злоумышленник, который может внедрить шаблон AngularJS, может использовать его для выполнения произвольного JavaScript .
- Если политика содержит
'unsafe-eval'
, инъекции вeval()
,setTimeout()
и несколько других редко используемых API.
Разработчики и инженеры по безопасности должны уделять особое внимание таким шаблонам во время проверок кода и аудитов безопасности. Более подробную информацию об этих случаях можно найти в документе «Политика безопасности контента: успешный беспорядок между усилением защиты и смягчением последствий» .
Дальнейшее чтение
- CSP мертв, да здравствует CSP! О небезопасности белых списков и будущем политики безопасности контента
- Оценщик CSP
- Конференция LocoMoco: Политика безопасности контента – успешный беспорядок между усилением защиты и смягчением последствий
- Доклад Google I/O: защита веб-приложений с помощью современных функций платформы