Как мы сделали чтение писем безопаснее: Content Security Policy в Яндекс.Почте

Как мы сделали чтение писем безопаснее: Content Security Policy в Яндекс.Почте

Одним из приоритетов для команды Яндекс.Почты всегда была и есть безопасность данных пользователя. Причем это касается не только хранения писем, но и безопасного доступа к ним. Еще в 2011 году мы стали пропускать все изображения в письмах через наши прокси-сервера, перекрыв один из каналов распространения вредоносного кода, а также кешировать их для экономии трафика и обеспечения большей приватности. В ноябре этого года мы внедрили шифрование при приеме и отправке почты, а также и перевели почту в режим HTTPS-only — теперь веб-интерфейс доступен только по безопасному протоколу.

А с недавних пор мы стали поддерживать новый механизм защиты данных пользователя – стандарт Content Security Policy. С его помощью можно запретить скриптам на странице подгружать какие-либо ресурсы с хостов, не указанных в белом списке.

Это пока довольно редкая штука (ни одна крупная известная нам почта этого ещё не применяет), и в этом посте мы поделимся опытом внедрения стандарта.

  • валидация пользовательского ввода и Web Application Firewall (WAF);
  • экранирование спецсимволов;
  • защита кук с помощью HttpOnly, чтобы их нельзя было прочитать из JavaScript;
  • различные плагины для браузера, например, noscript.

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 и имеют частичную поддержку стандартного.
Из чего состоит 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-отчеты о нарушениях. Вот так он выглядит:

Некоторые браузеры также указывают в отчете ссылку и строку JS, которые привели к нарушению политики безопасности.

  • 'self' — соответствует текущему хосту, протоколу и порту.
  • 'none' — все запрещено.
  • 'unsafe-inline' — используется в script-src и разрешает , javascript: и инлайн-обработчики событий (onclick=""). Для style-src разрешает использование тега и атрибута 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% в блокирующем режиме.

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

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

Разберем заголовок на примере мобильной Яндекс.Почты

  1. Оценка списка загружаемых ресурсов.
  2. Внедрение заголовка Content-Security-Policy-Report-Only.
  3. Анализ логов.
  4. Исправление политики.
  5. Внедрение заголовка Content-Security-Policy (переход в режим блокировки).
  6. Счастье пользователей :)
Что нужно учитывать при внедрении
  • 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 шлет очень неинформативные отчеты.
  • Если вы используете 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 уже начинают внедрять новые возможности из него.

📎📎📎📎📎📎📎📎📎📎