Перейти к основному содержанию
BeginPC.ru

Компьютер для новичков и не только

Content Security Policy

Эта информация не нужна обычным пользователям компьютера, но будет полезна для людей, занимающихся разработкой и администрированием сайтов. Как известно страницы сайта состоят из различных ресурсов, таких как скрипты, таблицы стилей, изображения, шрифты и так далее. Они могут загружаться как непосредственно с сервера, где расположен сам сайт, так и с любого другого домена в интернете. Это имеет свои преимущества и недостатки.

Серьезным минусом загрузки ресурсов из другого источника является потенциальная уязвимость внедрения вредоносного контента. Например, в виде межсайтовых скриптовых атак или на английском Cross Site Scripting (XSS) и внедрения зловредных данных. Браузер, загружая страницу доверяет всему контенту, который на ней присутствует, даже если это зловредный код, внедренный незаконно.

Он просто не может отличить один от другого. Как защитить сайт от атак злоумышленников с использованием XSS посвящена данная статья. Для решения подобного рода проблем и повышения безопасности веб-приложений был разработан специальный защитный механизм, подсказывающий браузеру какой контент является незаконным на данной странице.

Content Security Policy (CSP) — это политика безопасности контента устанавливаемая администратором сайта и в декларативной форме с помощью HTTP-заголовка уведомляющая посетителей о доверенных источниках (белых списках) откуда могут быть загружены ресурсы для данной страницы. Это позволяет браузеру клиента блокировать загрузку ресурсов из не доверенных источников, а разработчику получать отчеты о попытках нарушения установленной политики безопасности.

Другими словами, сервер только информирует о перечне доверенных источников и типах ресурсов на данной странице, а исполнение данных рекомендаций лежит полностью на клиентском ПО. Поэтому требуется поддержка данной технологии в браузере, с чем как всегда все не совсем гладко.

На сегодняшний день рекомендацией консорциума W3C является CSP Level 2 которую мы будем рассматривать. Идет работа над Level 3 пока имеющей статус рабочего проекта. Для информирования клиента об использовании политики безопасности контента служит HTTP заголовок Content-Security-Policy который может принимать различные директивы для управления загрузкой ресурсов. Если браузер не поддерживает эту возможность, то он просто игнорируется.

В заголовке перечисляются разрешенные URL для каждого типа контента отдельно. Поэтому даже если злоумышленник сможет внедрить свой код на страницу с домена evil.ru, то он не будет выполнен, если в директиве будет указано использовать только собственный домен.

В случае обнаружения нарушения безопасности на странице, данный ресурс блокируется и в консоли браузера появляется соответствующее предупреждение на подобии «Refused to load the script ...» с перечнем запрещенных к загрузке ресурсов и причинами блокировки.

сообщение о заблокированных ресурсах в соответствие с политикой безопасности контента

Правила формирования заголовка

Что такое Content Security Policy мы выяснили, способы добавления на сайт мы рассмотрим позже, а сейчас займемся самим заголовком. Главное правило, которое нужно запомнить. Запрещено все, что не разрешено в директиве в явном виде.

Заголовок должен соответствовать следующему формату. Название заголовка отделяется двоеточием, значения директив (directive) пишутся через пробел, директивы отделяются друг от друга точкой с запятой. Наследование не применяется, каждая директива должна содержать свой «белый список» разрешенных значений.

Допустимо указывать как просто разрешенный домен http://site.ru, так и отдельный каталог http://site.ru/catalog/ или конкретный файл на сервере http://site.ru/scripts/file.js. Так же можно использовать подстановочные знаки, например http://*.site.ru означает любые поддомены. Заголовок может быть одинаковым для всего сайта или каждая страница может использовать индивидуальный набор правил, что более правильно. Он может присутствовать только на некоторых страницах сайта.

Имеются специальные ключевые слова, которые записываются в одинарных кавычках.

  • 'self' — указывет на собственный домен
  • 'none' — пустое значение URL, а раз нет разрешенных, значит все запрещено
  • 'unsafe-inline' — используется в script-src и style-src для регулирования встроенных (inline) ресурсов.
  • 'unsafe-eval' — применяется в script-src и style-src для контроля за динамическим добавлением кода с помощью eval, setTimeout и подобного.
  • 'nonce-$random' — используется совместно со script-src и style-src для подписывания встраиваемых ресурсов одноразовыми случайными строками.
  • http:, https:, data:, blob:, filesystem:, mediastream: — позволяют указывать разрешенные схемы для подключаемого контента. Записываются без кавычек с двоеточием.

Директивы Content Security Policy

CSP предоставляет широкий набор директив для разных случаев использования и они являются регистронезависимыми.

default-src — устанавливает значение по умолчанию для других директив, если они не заданы в явном виде. Рассмотрим несколько примеров.

Любые внешние ресурсы могут загружаться только с текущего домена, но запрещены встроенные, динамические, data: и остальные.

Content-Security-Policy: default-src 'self';

Любые ресурсы могут загружаться из любых источников. Не распространяется на встроенные и динамически добавляемые ресурсы.

Content-Security-Policy: default-src *;

Дополнительно разрешает инлайн и динамически подключаемые ресурсы.

Content-Security-Policy: default-src * 'unsafe-inline' 'unsafe-eval';

Устанавливает для всех директив значение по умолчанию «нет», в результате все ресурсы страницы окажутся заблокированными и загрузится только html документ. Рекомендуется использовать, чтобы затем прописать правила только для тех ресурсов, которые реально используются на сайте.

Content-Security-Policy: default-src 'none';

Задает для всех ресурсов необходимость использования HTTPS протокола, все что подключается с использованием обычного HTTP загружено не будет.

Content-Security-Policy: default-src https:;

script-src — задает разрешенный список источников контента для внешних скриптов JavaScript и других сценариев наподобие таблиц стилей XSLT.

Значение по умолчанию для всех ресурсов установлено собственный домен, но явное указание директивы script-src отменяет заданное для нее базовое значение. В результате загрузка скриптов разрешена только с http://example.ru, поэтому сценарии с собственного домена окажутся заблокированными.

Content-Security-Policy: default-src 'self'; script-src http://example.ru

Чтобы это исправить это нужно прямо разрешить свой домен для загрузки JavaScript.

Content-Security-Policy: default-src 'self'; script-src 'self' http://example.ru

Разрешает загрузку ресурсов из любого источника, но скрипты разрешены только с текущего хоста, site.ru и файл main.js расположенный по адресу example.ru/scripts/main.js

Content-Security-Policy: default-src *; script-src 'self' http://site.ru http://example.ru/scripts/main.js

Данные правила действуют только на подключение внешних ресурсов. Чтобы разрешить динамическое добавление скриптов используется слово 'unsafe-eval'.

Content-Security-Policy: script-src 'self' 'unsafe-eval'

Аналогично для разрешения запуска встроенных ресурсов используется ключевое слово 'unsafe-inline'. Проблема при этом в том, что сильно снижается уровень безопасности. Поэтому для более точного контроля за инлайн ресурсами существует ключевое слово 'nonce-$random', где $random это случайная одноразовая строка.

Смысл в том, что сервер каждый раз при запросе страницы генерирует ее заново и подставляет в заголовок Content-Security-Policy, одновременно добавляя в качестве значения атрибута nonce в тег скрипта. Это можно использовать как с внешними, так и со встроенными скриптами.

Content-Security-Policy: default-src 'self'; script-src 'self' http://site.ru 'nonce-Dzh7bu3akGP5eLq28mcays14own'

Браузер сверяет значение из заголовка со значением, указанным в тэге скрипта и если они не совпадают или в скрипте он отсутствует, то такие сценарии блокируются. Поэтому в приведенном далее примере исполнится только второй и четвертый сценарии, хотя домен any.ru в правилах явно не указан. Использование 'unsafe-inline' заметно снижает эффективность CSP, поэтому в целях безопасности одноразовое значение должно быть трудно подбираемым. В связи с чем рекомендуется использование криптографически стойкого генератора случайных чисел для его создания.

<script>alert("Заблокировано, встроенные скрипты не разрешены.")</script>
<script nonce="Dzh7bu3akGP5eLq28mcays14own">
alert("Будет выполнен, поскольку имеет nonce.")
</script>
<script nonce="jD9cn17SAd3wc3Sasdfn939hc3x">
alert("Заблокировано, поскольку использован неверный nonce.")
</script>
<script nonce="Dzh7bu3akGP5eLq28mcays14own" src="http://any.ru/calc.js" ></script>

Альтернативным вариантом для встроенных сценариев является использование их хэша. Он обеспечивает большую надежность, однако сильнее нагружает браузер. Для этого вычисляется хэш кода, расположенного между тегами <scrypt> по алгоритму SHA-256, SHA-384 или SHA-512 и затем кодируется с помощью base64. Получившейся результат добавляется в заголовок с указанием использованного алгоритма хеширования. Так для сценария alert('Привет мир!'); это выглядит так.

Content-Security-Policy: script-src 'self' 'sha256-XF0hCAzyn4g4vPuj8fiU2TUS+icXk5gEvLCZ1BADXZA='

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

<script>alert('Привет мир!');</script>
<script> alert('Привет мир!');</script>
<script>
alert('Привет мир!');
</script>

style-src — белый список разрешенные источников для каскадных таблиц стилей документа. Здесь все тоже самое, что и в script-src.

Стили могут дополнительно загружаться из папки site.ru/catalog/css и ее подпапок только по защищенному протоколу, все остальное только с собственного домена, так же разрешены встроенные сценарии.

Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' https://site.ru/catalog/css/

connect-src — адреса с которыми может устанавливаться соединение с помощью интерфейсов JavaScript. Под ее действие попадают XMLHttpRequest, WebSocket, EventSource и подобное.

font-src — регламентирует разрешенные источники загрузки шрифтов. Например, если в директиве default-src задан собственный домен, а ваш сайт загружает веб-шрифты с сервиса google, то необходимо явно разрешить загрузку из внешнего источника.

Content-Security-Policy: font-src https://fonts.gstatic.com

child-src — используется для контроля за фреймами, заменило устаревший frame-src.

Устанавливает для контента следующие directive. Фреймы запрещены, стили со своего сервера и https://example.ru, скрипты со собственного домена и https://site.ru включая все его поддомены, остальное только с текущего хоста.

Content-Security-Policy: default-src 'self'; script-src 'self' https://*.site.ru; style-src 'self' https://example.ru; child-src 'none'

frame-ancestors — указывает каким образом должен встраиваться ресурс (frame, iframe, embed, object ...) на страницу. Служит заменой для HTTP заголовка X-Frame-Options

base-uri — ограничивает URL которые могут использоваться для указания базового адреса в теге <base> документа.

Content-Security-Policy: base-uri 'self'

form-action — регламентирует адреса, на которые могут быть отправлены формы на странице.

img-src — список разрешенных источников для загрузки изображений. Изображения, встраиваемые в страницу с помощью data, нужно разрешать отдельно.

Допустимы изображения только с собственного домена, остальное с любого адреса по защищенному протоколу.

Content-Security-Policy: default-src https://*; img-src 'self'

Запрещены любые изображения в том числе со своего хоста, но разрешены подключенные через data

Content-Security-Policy: default-src *; img-src data:

media-src — регулирует источники аудио, видео и связанных текстовых данных.

object-src — указывает возможные источники для загрузки плагинов на страницу.

plugin-types — регулирует разрешенный тип встраиваемых плагинов на основе их MIME типа. Например, разрешает только использование Flash.

Content-Security-Policy: plugin-types application/x-shockwave-flash

report-uri — каждая попытка загрузки ресурса из неразрешенного источника или запрещенного типа считается нарушением политики безопасности контента. В этой директиве указывается URL адрес, по которому браузер пользователя будет отсылать POST запрос в формате JSON о такой ситуации. Не работает, в случае добавления заголовка с помощью тега meta.

Content-Security-Policy: default-src 'self'; report-uri http://site.ru/csp-error

Если на странице http://site.ru/example будет обнаружено нарушение политики безопасности контента, то на адрес http://site.ru/csp-error будет отправлен отчет о каждой попытке нарушения отдельно. Соответственно по этому адресу должен располагаться скрипт, который будет обрабатывать поступающую информацию. Вид отчета зависит от типа нарушения, это пример для встроенного скрипта.

{
    "csp-report": {
        "blocked-uri":"inline",
        "column-number":1,
        "document-uri":"http://site.ru/example",
        "line-number":8,
        "original-policy":"default-src 'self'; report-uri http://site.ru/csp-error",
        "referrer":"http://site.ru/",
        "source-file":"http://site.ru/example",
        "violated-directive":"default-src"
    }
}

sandbox — помещает страницу в песочницу и определяет разрешенные действия, используются такие же флаги как и для iframe.

Мы рассмотрели возможности имеющиеся в CSP Level 2 по защите сайта от Cross Site Scripting и подмены контента. Несмотря на очевидные преимущества он не используется в интернете на сегодняшний день достаточно широко. Этому есть простое объяснение. Для его внедрения нужно приложить много усилий и зачастую переписать часть кода для обеспечения надлежащего уровня защиты.

Все относительно просто, когда для загрузки ресурсов используется только собственный домен или несколько заранее известных сторонних адресов. Среднестатистический сайт в интернете использует счетчики посещаемости, различные рекламные сети и виджеты. Все это тянет за собой шлейф из десятков дополнительно загружаемых на страницу ресурсов с самых разных URL зачастую неизвестных заранее.

В этой ситуации настройка становится совсем не тривиальной задачей. Однако даже в этом случае, политика безопасности контента может найти свое применение, например для защиты административной части сайта, страницы оплаты или других важных разделов. Это поможет создать дополнительный барьер от кражи сессий, паролей и другой конфиденциальной информации.

Стоит иметь в виду, что антивирусы могут вмешиваться в работу сайта. Например, Касперский встраивает в страницу собственный скрипт для ее проверки, поэтому когда он обнаруживает в ответе сервера этот заголовок, то он его модернизирует на лету и добавляет правила разрешающие ему выполняться на странице, чтобы браузер его не заблокировал.

Заголовок Content-Security-Policy-Report-Only

Данный заголовок аналогичен предыдущему, с той лишь разницей, что он не блокирует загрузку ресурсов, а только информирует о факте нарушения политики безопасности контента.

Его удобно использовать для отладки политики и сбора статистики. Может применяться на рабочем сайте, так как ничем не выдает свое присутствие для простых посетителей и не создает им проблем. В случае обнаружения нарушений безопасности в консоли будут выводиться соответствующие сообщения, а если указана директива report-uri, то по указанному адресу будет высылаться отчет. При этом нужно учитывать, что при высокой посещаемости сайта поток отчетов может быть высоким.

Как добавить HTML-заголовок на страницу

Здесь все аналогично другим HTTP заголовкам и существует множество вариантов, как это можно сделать. Вот некоторые из них, но помните, что в идеале для каждой страницы сайта должен быть свой набор правил чтобы свести площадь атаки к минимуму.

Сервер Apache, конфигурационный файл httpd.conf

Header set Content-Security-Policy "default-src 'self';"

Сервер Nginx, конфигурационный файл nginx.conf

add_header Content-Security-Policy "default-src 'self'";

Файл .htaccess

<ifModule mod_headers.c> 
    Header set Content-Security-Policy "default-src 'self'" 
</ifModule>

Язык PHP

header("Content-Security-Policy: default-src 'self'");

HTML метатег meta добавляется в head страницы. Данный тег не действует на ресурсы, расположенные на странице перед ним. Не может использоваться для Content-Security-Policy-Report-Only

<meta http-equiv="Content-Security-Policy" content="default-src 'self'">

Что делать, если вы не добавляли данный заголовок, но получаете ошибки в консоли связанные с CSP. Значит он добавляется автоматически сервером, CMS или плагином. Нужно найти источник его появления и отключить его вывод или настроить нужным вам образом. Если такой возможно нет, то можно попробовать его переопределить, добавив собственный с нужными настройками.

Применение CSP при грамотной настройке позволяет существенно повысить безопасность и снизить риск межсайтового скриптинга. Правда для этого нужно отказаться от использования unsafe-unline и unsafe-eval, что бывает не просто.