Как мы сделали чтение писем безопаснее: 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 и имеют частичную поддержку стандартного.
- В текущей версии стандарта доступен следующий набор директив:
- 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, позволяющее добавлять заголовки на страницу и тестировать политики. для разбора отчетов браузеров, чтобы проще получать список блокируемых ресурсов.
Разберем заголовок на примере мобильной Яндекс.Почты
- Оценка списка загружаемых ресурсов.
- Внедрение заголовка 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 шлет очень неинформативные отчеты.
- Если вы используете 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 уже начинают внедрять новые возможности из него.