Одним из приоритетов для команды Яндекс.Почты всегда была и есть безопасность данных пользователя. Причем это касается не только хранения писем, но и безопасного доступа к ним. Еще в 2011 году мы стали пропускать все изображения в письмах через наши прокси-сервера, перекрыв один из каналов распространения вредоносного кода, а также кешировать их для экономии трафика и обеспечения большей приватности. В ноябре этого года мы внедрили шифрование при приеме и отправке почты, а также и перевели почту в режим HTTPS-only — теперь веб-интерфейс доступен только по безопасному протоколу.
А с недавних пор мы стали поддерживать новый механизм защиты данных пользователя – стандарт Content Security Policy. С его помощью можно запретить скриптам на странице подгружать какие-либо ресурсы с хостов, не указанных в белом списке.
Это пока довольно редкая штука (ни одна крупная известная нам почта этого ещё не применяет), и в этом посте мы поделимся опытом внедрения стандарта.
XSS, несмотря на всю изученность, является одной из самых распространенных уязвимостей сайтов. Эта уязвимость позволяет злоумышленнику вставить вредоносный код на страницу веб-приложения. Как контролировать это на сервере, мы прекрасно знаем. А вот на пользовательской стороне с этим все несколько сложнее.
Какие методы защиты мы знаем?
- валидация пользовательского ввода и Web Application Firewall (WAF);
- экранирование спецсимволов;
- защита кук с помощью HttpOnly, чтобы их нельзя было прочитать из JavaScript;
- различные плагины для браузера, например, noscript.
Теперь появился еще один механизм защиты от такого рода атак — Content Security Policy
Content Security Policy (CSP) — новый стандарт, определяющий HTTP-заголовки Content-Security-Policy и Content-Security-Policy-Report-Only, которые сообщают браузеру белый список хостов, с которых он может загружать различные ресурсы.
Текущий статус стандарта — Candidate Recommendation.
На сегодняшний день его поддерживают все популярные браузеры:
- Chrome 25+, Firefox 23+, Opera 15+ и Яндекс.Браузер имеют полную поддержку и понимают стандартный заголовок;
- Firefox 4-22, IE 10+ поддерживают нестандартный заголовок X-Content-Security-Policy и имеют частичную поддержку стандартного;
- Chrome 14-24, Safari 5-7 поддерживают нестандартный заголовок X-Webkit-CSP и имеют частичную поддержку стандартного.
Для ознакомления можно почитать на HTML5 Rocks статью Майка Уэста (Mike West) — одного из разработчиков стандарта. Хорошее описание стандарта есть на MDN.
Из чего состоит CSP?
Заголовок CSP состоит из набора разделенных директив — частей политики, контролирующих определенные ресурсы браузера.
- В текущей версии стандарта доступен следующий набор директив:
- default-src указывает список хостов, которые по умолчанию присваиваются неуказанным директивам.
- script-srс, style-src, object-src (для плагинов вроде Flash), img-src, media-src (audio и video), frame-src (iframe), font-src, connect-src (XMLHttpRequest, WebSocket, EventSource) — более узкие директивы, контролирующие соответствующие ресурсы браузера. Для каждой директивы надо указать список хостов (не урлов), с которыми может общаться браузер. Можно использовать *.
- report-uri — указывает URL, на который будут отправляться JSON-отчеты о нарушениях. Вот так он выглядит:
{
"csp-report": {
"document-uri": "https://mail.yandex.ru/neo2/",
"referrer": "http://www.yandex.ru/",
"violated-directive": "script-src 'unsafe-inline' 'unsafe-eval' blob: chrome-extension: *.yandex.ru *.yandex.net yandex.st",
"original-policy": ".... здесь указана все политики ...",
"blocked-uri": "... урл заблокированного ресурса ..."
}
}
Некоторые браузеры также указывают в отчете ссылку и строку JS, которые привели к нарушению политики безопасности.
Кроме того, в директивах можно использовать не хосты, а ключевые слова (обязательно в кавычках):
- 'self' — соответствует текущему хосту, протоколу и порту.
- 'none' — все запрещено.
- 'unsafe-inline' — используется в script-src и разрешает
<script>
, javascript: и инлайн-обработчики событий (onclick=""). Для style-src разрешает использование тега<style>
и атрибута style="". По возможности старайтесь не указывать это ключевое слово, т.к. это напрямую разрешает исполнять любой инлайн javascript на странице, что может приводить к XSS. - 'unsafe-eval' — используется в script-src и разрешает любую кодогенерацию: eval, new Function, setTimeout(' var foo = «bar» ', 1).
Хосты можно указывать как просто "yandex.st"
, так и с протоколом или портом "https://yandex.st:443"
. Помните, что если у хоста не указан протокол или порт, то он берется из текущей страницы, по аналогии с same origin policy. Таким образом, хост "yandex.st"
на странице "https://mail.yandex.ru:443/neo2/"
автоматически приобретет вид "https://yandex.st:443"
.
Если указан заголовок Content-Security-Policy, то браузер блокирует все ресурсы, которые не соответствуют политике. Заголовок Content-Security-Policy-Report-Only также проверяет все ресурсы, но не блокирует их, а только сообщает о нарушениях. Мы рекомендуем его использовать на первых этапах внедрения CSP.
Как мы внедряли
Мы исследовали эту технологию с весны, когда еще не было ни одной стандартной реализации. Сначала аккуратно внедрили заголовок Content-Security-Policy-Report-Only для нашей внутренней почты. Некоторые время мы изучали отчеты и исправляли нашу политику, и уже в мае включили CSP в блокирующем режиме. Во время внутреннего тестирования исследовалось поведение всех заголовков — и стандартных, и нет.
С публичной почтой было несколько труднее, ведь к нам приходили пользователи с неконтролируемым окружением. Несколько месяцев мы разбирались в отчетах, исправляли политики, и, наконец, осенью выкатили их на 100% в блокирующем режиме.
Самое интересное, что мы теперь имеем — несколько миллионов ежедневных отчетов с нарушениями от вирусов, нерадивых плагинов и расширений, которые пытаются вставлять свой код на наши страницы. Мы не можем контролировать содержимое этого кода, а значит не можем гарантировать, что он бережно относится к персональным данным наших пользователей — и довольны тем, что блокируем их исполнение.
Кроме того, в процессе внедрения мы разработали инструменты для работы с CSP:
- Расширение для браузеров, основанных на Chromium, позволяющее добавлять заголовки на страницу и тестировать политики.
- Утилита для разбора отчетов браузеров, чтобы проще получать список блокируемых ресурсов.
Разберем заголовок на примере мобильной Яндекс.Почты
// разрешаем соединение с собственным хостом, yandex.st и подключения по WebSocket к xiva-daria.mail.yandex.net
Content-Security-Policy: default-src 'self' wss://xiva-daria.mail.yandex.net yandex.st;
// разрешаем собственные домены для подновления кук при длительной работе
frame-src *.yandex.ru;
// 'self' data: для inline-картинок
// mc.yandex.ru — Яндекс.Метрика
// yandex.st — наш CDN со статикой
// *.yandex.net - аватарки и https-кешер
img-src 'self' data: mc.yandex.ru *.yandex.net yandex.st;
// шрифтов и плагинов у нас нет
font-src 'none';
object-src 'none';
// 'unsafe-inline' — мы используем inline-стили для анимации переходов между страницами
// yandex.st — наш CDN со статикой
style-src 'unsafe-inline' yandex.st;
// self и blob — для разрешения выполнения js через blob
// unsafe-eval потому что мы используем кодогенерацию
// mc.yandex.ru — Яндекс.Метрика
// yandex.st — наш CDN со статикой
script-src 'self' 'unsafe-eval' blob: mc.yandex.ru yandex.st;
// обязательно указываем урл для отчетов
report-uri /neo2/csp.jsx?from=touch
Примерный план внедрения CSP на сервисе выглядит следующим образом:
- Оценка списка загружаемых ресурсов.
- Внедрение заголовка Content-Security-Policy-Report-Only.
- Анализ логов.
- Исправление политики.
- Внедрение заголовка Content-Security-Policy (переход в режим блокировки).
- Счастье пользователей :)
Что нужно учитывать при внедрении
Особенности браузеров:
- Safari 5 и AndroidBrowser с заголовком X-Webkit-CSP имеют очень плохую реализацию стандарта. И мы советуем вам вообще не использовать CSP для этих браузеров. Например, они плохо понимают правила unsafe-eval и unsafe-inline.
- Firefox в X-Content-Security-Policy реализует немного нестандартные директивы. Вместо connect-src нужно писать xhr-src (или можно добавить правила в default-src). Кроме того, он не понимает unsafe-inline, unsafe-eval, вместо них надо дописывать директиву «options inline-script eval-script». Подробнее про собственную реализацию заголовка можно почитать на вики Mozilla.
- У Firefox и X-Content-Security-Policy есть проблемы с report-ui.
- Safari в iOS6 шлет очень неинформативные отчеты.
Мы не рекомендуем использовать нестандартные заголовки. Последние версии современных браузеров (за исключением IE) поддерживают стандартный заголовок, поэтому, используя только его, вы покрываете подавляющую часть аудитории.
Особенности CSP, о которых нужно всегда помнить:
- Если вы используете inline-картинки, то в img-src нужно разрешить протокол «data:».
- Используя Blob, указывайте 'self' (для Chrome) и blob: (для Firefox и X-WebKit-CSP)
- CSP верхнего документа не распространяется на дочерние iframe за исключением about:blank.
- Если вы не хотите блокировать расширения хрома, которые, кстати, с последних версий тоже живут по CSP, то надо разрешить протокол «chrome-extension:»
- default-src распространяется на все неуказанные директивы, но если вы захотите что-то добавить или удалить, то придется указывать все домены заново.
- Можно одновременно указывать Content-Security-Policy и Content-Security-Policy-Report-Only. Оба заголовка будут работать независимо. Такой подход будет полезен для тестирования новых политик.
- Если вы используете фреймворки с feature detection (например, jQuery < 1.8 или Modernizr), то для style-src надо указать 'unsafe-inline'.
Естественно, это не всегда возможно, но старайтесь не использовать *, а указывайте точный список доменов. Также указывайте все директивы, иначе все ошибки будут сыпаться как нарушение в default-src. Конечно, размер заголовка увеличиться, зато найти проблемы будет намного проще.
К сожалению, нам пока не удалось отказаться от unsafe-inline. Инлайн-скрипты в почте используются для двух вещей: выдача настроек и проверка загрузки критичных JS (jQuery и загрузчика). И если настройки мы могли переделать на JSON, то отказаться от важных отчетов незазгрузки JS мы не смогли, и пришлось оставить 'unsafe-inline'. Также проблем добавило активное использование инлайн-атрибутов в WYSWYG-редакторе TinyMCE.
Хоть такое правило и позволяет исполнять любой инлайн javascript на странице, мы можем обезопасить себя тем, что разрешаем соединение только с проверенными ресурсами.
Обновленный стандарт CSP 1.1
Новая версия стандарта на текущий момент находится в стадии черновика, но Chrome и Firefox уже начинают внедрять новые возможности из него.
Например, новая директива nonce может стать решением неудобства с полным запретом unsafe-inline. Схема работы пока находится в стадии разработки, но мы прикладываем усилия, чтобы он работал следующим образом:
- nonce — случайная последовательность символов передаваемых в заголовке;
- если есть unsafe-inline и nonce, то nonce выключает unsafe-inline;
- выполняются только те инлайн-скрипты, которые подписаны атрибутом nonce с той же последовательностью символов.
Пример:
Content-Security-Policy: default-src 'self'; script-src 'self' https://example.com 'nonce-eef8264c4994bf6409c51ac7c9614446'
<script>
alert("Заблокирован, отсутствует атрибут nonce")
</script>
<script nonce="22168992a8d57a5d3a64ca73bb9fc669">
alert("Заблокирован, потому что атрибут nonce не совпадает")
</script>
<script nonce="eef8264c4994bf6409c51ac7c9614446">
alert("Выполнен, потому что атрибут nonce валиден")
</script>
<!-- валиден, потому что в script-src есть https://example.com -->
<script src="https://example.com/allowed-because-of-src.js"></script>
<!-- Заблокирован, потому что атрибут nonce не совпадает -->
<script nonce="22168992a8d57a5d3a64ca73bb9fc669" src="https://otherdomain.com/invalid.js"></script>
<!-- Выполнен, потому что атрибут nonce совпадает, несмотря на то, что otherdomain.com нет в директиве script-src -->
<script nonce="eef8264c4994bf6409c51ac7c9614446" src="https://otherdomain.com/valid.js"></script>
Также CSP 1.1 добавляет новые возможности:
- указание политики через метатег;
- JavaScript-API для получения и проверки политик;
- DOM-событие о нарушении политики;
- новые директивы form-action, plugin-types.
В заключение
CSP оказался не только защитой от атак и блокированием непонятных запросов. В тестировании нас приятно удивили две вещи:
- По сути CSP дает возможность сделать из компьютеров пользователей распределенную систему тестирования безопасности. И если резко увеличивается количество отчетов о нарушениях, это повод пойти искать проблему.
- Так как все пользователи почты работают по HTTPS, CSP дал возможность отследить и исправить те укромные места, в которых запросы все еще шли по HTTP.
Пробуйте CSP, внедряйте, защищайте личные данные пользователей и просто делайте мир лучше.
Автор: doochik