Уменьшите риск межсайтового скриптинга (XSS) с помощью строгой политики безопасности контента (CSP).

Поддержка браузера

  • Хром: 52.
  • Край: 79.
  • Фаерфокс: 52.
  • Сафари: 15.4.

Источник

Межсайтовый скриптинг (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 на основе nonce.

Строгий 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, вам необходимо:

  1. Решите, должно ли ваше приложение устанавливать CSP на основе одноразового номера или хэша.
  2. Скопируйте CSP из раздела структуры Strict CSP и установите его в качестве заголовка ответа во всем приложении.
  3. Выполните рефакторинг HTML-шаблонов и клиентского кода для удаления шаблонов, несовместимых с CSP.
  4. Разверните свой CSP.

Вы можете использовать аудит Lighthouse (v7.3.0 и выше с флагом --preset=experimental ) на протяжении всего этого процесса, чтобы проверить, есть ли на вашем сайте CSP и достаточно ли он строг, чтобы быть эффективным против XSS.

Отчет Lighthouse предупреждает, что в режиме принудительного применения не найден ни один CSP.
Если на вашем сайте нет CSP, Lighthouse покажет это предупреждение.

Шаг 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 в серверные платформы:

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}' .

Загружать исходные скрипты динамически

Вы можете динамически загружать сторонние скрипты, используя встроенный скрипт.

Пример того, как встроить ваши скрипты.
Разрешено CSP
<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>
Чтобы разрешить запуск этого сценария, необходимо вычислить хэш встроенного сценария и добавить его в заголовок ответа CSP, заменив заполнитель {HASHED_INLINE_SCRIPT} . Чтобы уменьшить количество хешей, вы можете объединить все встроенные скрипты в один скрипт. Чтобы увидеть это в действии, обратитесь к этому примеру и его коду .
Заблокировано CSP
<script src="https://example.org/foo.js"></script>
<script src="https://example.org/bar.js"></script>
CSP блокирует эти сценарии, поскольку они не были добавлены динамически и не имеют атрибута integrity , соответствующего разрешенному источнику.

Рекомендации по загрузке скрипта

В примере встроенного скрипта добавлено 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 блокирует несовместимый шаблон.

Отчеты о нарушении CSP в консоли разработчика Chrome.
Ошибки консоли для заблокированного кода.

В большинстве случаев исправить это просто:

Рефакторинг встроенных обработчиков событий

Разрешено CSP
<span id="things">A thing.</span>
<script nonce="${nonce}">
  document.getElementById('things').addEventListener('click', doThings);
</script>
CSP позволяет использовать обработчики событий, зарегистрированные с помощью JavaScript.
Заблокировано CSP
<span onclick="doThings();">A thing.</span>
CSP блокирует встроенные обработчики событий.

Рефакторинг javascript: URI

Разрешено CSP
<a id="foo">foo</a>
<script nonce="${nonce}">
  document.getElementById('foo').addEventListener('click', linkClicked);
</script>
CSP позволяет использовать обработчики событий, зарегистрированные с помощью JavaScript.
Заблокировано CSP
<a href="javascript:linkClicked()">foo</a>
CSP блокирует javascript: URI.

Удалите eval() из вашего JavaScript.

Если ваше приложение использует eval() для преобразования сериализации строк JSON в объекты JS, вам следует провести рефакторинг таких экземпляров в JSON.parse() , что также быстрее .

Если вы не можете удалить все случаи использования eval() , вы все равно можете установить строгий CSP на основе nonce, но вам придется использовать ключевое слово CSP 'unsafe-eval' , что делает вашу политику немного менее безопасной.

Вы можете найти эти и другие примеры такого рефакторинга в этой строгой кодовой лаборатории CSP:

Шаг 4 (необязательно). Добавьте резервные варианты для поддержки старых версий браузера.

Поддержка браузера

  • Хром: 52.
  • Край: 79.
  • Фаерфокс: 52.
  • Сафари: 15.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 в промежуточной, а затем в производственной среде:

  1. (Необязательно) Разверните CSP в режиме «только отчет», используя заголовок Content-Security-Policy-Report-Only . Режим «Только отчеты» удобен для тестирования потенциально критических изменений, таких как новый CSP в рабочей среде, прежде чем вы начнете применять ограничения CSP. В режиме только отчетов ваш CSP не влияет на поведение вашего приложения, но браузер по-прежнему генерирует консольные отчеты об ошибках и нарушениях, когда обнаруживает шаблоны, несовместимые с вашим CSP, поэтому вы можете увидеть, что могло бы сломаться для ваших конечных пользователей. Дополнительную информацию см. в разделе Reporting API .
  2. Если вы уверены, что ваш 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') ), в том числе в любые библиотечные функции, создающие узлы DOM script на основе значений их аргументов. Сюда входят некоторые распространенные API, такие как .html() в jQuery, а также .get() и .post() в jQuery < 3.0.
  • Если в старых приложениях AngularJS есть внедрение шаблонов. Злоумышленник, который может внедрить шаблон AngularJS, может использовать его для выполнения произвольного JavaScript .
  • Если политика содержит 'unsafe-eval' , инъекции в eval() , setTimeout() и несколько других редко используемых API.

Разработчики и инженеры по безопасности должны обращать особое внимание на такие шаблоны во время проверок кода и аудитов безопасности. Более подробную информацию об этих случаях можно найти в документе «Политика безопасности контента: успешный беспорядок между усилением защиты и смягчением последствий» .

Дальнейшее чтение