Компания
599,93
рейтинг
18 декабря 2013 в 16:15

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

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

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

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

image

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 и разрешает , 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% в блокирующем режиме.

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

    Кроме того, в процессе внедрения мы разработали инструменты для работы с 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 на сервисе выглядит следующим образом:
    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 шлет очень неинформативные отчеты.

    Мы не рекомендуем использовать нестандартные заголовки. Последние версии современных браузеров (за исключением 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. Схема работы пока находится в стадии разработки, но мы прикладываем усилия, чтобы он работал следующим образом:
    1. nonce — случайная последовательность символов передаваемых в заголовке;
    2. если есть unsafe-inline и nonce, то nonce выключает unsafe-inline;
    3. выполняются только те инлайн-скрипты, которые подписаны атрибутом 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 оказался не только защитой от атак и блокированием непонятных запросов. В тестировании нас приятно удивили две вещи:
    1. По сути CSP дает возможность сделать из компьютеров пользователей распределенную систему тестирования безопасности. И если резко увеличивается количество отчетов о нарушениях, это повод пойти искать проблему.
    2. Так как все пользователи почты работают по HTTPS, CSP дал возможность отследить и исправить те укромные места, в которых запросы все еще шли по HTTP.

    Пробуйте CSP, внедряйте, защищайте личные данные пользователей и просто делайте мир лучше.
Автор: @doochik
Яндекс
рейтинг 599,93

Комментарии (26)

  • +8
    Вопрос не по теме, но у вас когда-нибудь появится двухфакторная авторизация?
    • +5
      Когда-нибудь появится )
    • +3
      Поддерживаю, очень бы хотелось авторизацию через телефон. А в целом спасибо за вашу работу! За последний год пересела на Яндекс Почту полностью. Недавно зашла на Gmail — показалось крайне неудобно и медленно.
  • 0
    > Если вы не хотите блокировать расширения хрома, которые, кстати, с последних версий тоже живут по CSP, то надо разрешить протокол «chrome-extension:»
    Поясните пожалуйста, что имеется в виду. Расширения работают в отдельном окружении и имеют доступ к DOM такой же, как и стандартный код.
    • 0
      Если в manifest.json указаны content_scripts, то хром их вставляет в DOM как обычные скрипты. При этом src у них начинается с chrome-extension://. C CSS тоже самое.
      • 0
        Или я не очень понимаю, о чем вы, или вы ошибаетесь.
        gist.github.com/anonymous/8027276 — сделал небольшой gist, добавил в Хром. Зашел на mail.yandex.ru — есть alert. Скрипт в DOM не добавляется (ну это как бы и логично)
        • 0
          Знаете, вы правы :)
          Для новых хромов chrome-extension: и правда ничего не дает. Я сейчас погрепал старые логи и максимум, что нашел — Chrome 22

          "blocked-uri":"chrome-extension://bgeakjmfknncppbmgkkfbglnodccdecp"
          • 0
            Думаю просто это совсем другой кейс — расширения на то и расширения, чтобы не трогать скрипты сайта (хотя это возможно), а именно расширять функциональность. А CSP у вас — это круто и как всегда на уровне и до мелочей.
  • +9
    Интересно, как вы определяете границы безопасности и паранойи?

    Ваш валидатор в почте не понимает ссылки формата //yandex.ru вырезая атрибут href из ссылок напроч (я письмо в саппорт написал, обещали разобраться). Но я и ранее указывал на недостатки того, как вы игнорирует существование других стандартов, но как и сейчас было обещание разобраться и только.
    Для примера если я напишу URI с указанием конкретного протокола ( sip:pupkin@example.com) то вы переделаете его в sip:<a href="mailto:pupkin@example.com" >pupkin@example.com</a> невзирая на прямое указание протокола sip:.
    Интернет это не только mail и http. это еще и xmpp: ftp: tel: и прочие
    • +1
      Спасибо за указание на ошибки.

      В первом случае поведение верное, урль //yandex.ru является относительным в смысле схемы, не имеет смысла в письмах и является ошибкой, но мы можем просто форсить его в схему https.

      Во втором случае ошибка, скорее, на нашей стороне.
  • +1
    report-uri — указывает URL, на который будут отправляться JSON-отчеты о нарушениях… Некоторые браузеры также указывают в отчете ссылку и строку JS, которые привели к нарушению политики безопасности.

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

    смущает что владелец ресурса может получать в отчетах CSP ссылки. ведь ссылки могут содержать конфиденциальные данные (допустим, индентификатор пользователя в социальной сети, секретные ключи и т.п.). можно-ли намеренно «запретить» политиками CSP интересующие домены, чтобы затем получать эти сведения в отчетах?

    гипотетический сценарий. допустим есть виджет социальной сети (//yy.ru/widget.js), который смотрит в куки своего домена и делает ajax-запрос к сервису авторизации, передавая логин в урле (//passport.yy.ru/?login=username). штатными средствами мониторить ajax запросы браузер не даст. добавляем виджет на страницу, политиками разрешаем загрузку виджета c домена yy.ru, но запрещаем доступ к домену авторизации passport.yy.ru. когда на сцену выходит CSP в отчетах получаем урлы с логинами, по которым пользователей можно идентифицировать. профит.

    если все это нереальный бред про паранойю, буду признателен за конструктивную критику. :)
    • +2
      Да, в принципе, такое возможно, но CSP тут нового ничего не вносит. Владельцу сайта никто и раньше не мешал переопределить XMLHttpRequest и смотреть на все ajax-запросы.

      Что касательно отчетов, то в CSP 1.0 управлять отчетами нельзя, их можно только слать или не слать, и их формирует браузер. А в CSP 1.1 будет DOM-событие о нарушении безопасности и там уже можно будет что-то с ними сделать.
  • –1
    Grammar-Nazi негодует на дизайнера вашей картинки.
    • 0
      Это вы про Пушкина?
      • 0
        Это я про virusess
        • +3
          Ну доменные имена у сайтов с вирусами и не такие бывают :)
  • 0
    Отлично :)
    Может здесь ответят с чем связана странная работа яндекс почты последних 3 недели?
    В частности корпоративной. У двух клиентов с разницей в 1 день оказались пустыми ящики и адресные книги у меня при чтении писем яндекс упорно рекомендовал завести новый ящик.
    • 0
      Дайте больше подробностей в личку, пожалуйста.
  • 0
    Немного не в тему.
    Ребята, вы сделали аналитику для рассылок, но нигде не рассказали как ей пользоваться. Может быть, сделаете краткий мануал? Интересный функционал, но непонятно как с ним работать.
    • +1
      Помощь не помогает? help.yandex.ru/postoffice/

      Какие у вас вопросы?
      • –2
        Непонятно как делать рассылки.
        • +1
          Простите, но я вас не понимаю.
          Вы правильно сказали: мы сделали инструмент для анализа рассылок.
          Логично, что он не умеет делать рассылки, он умеет рассказывать что сталось с письмами, отправленными на Яндекс.Почту.

          mail.yandex.ru/promo-postoffice
      • 0
        Сделайте пожалуйста подтверждение владения доменом через DNS-запись, не всегда существует сайт.
        • 0
          Можно провалидировать e-mail.
        • 0
          Пока что можно подтвердить владение доменом в Почте для доменов или же в Вебмастере — на этих сервисах есть подтверждение через DNS. Пользоваться этими сервисами после подключения домена не обязательно (хотя мы были бы рады новым пользователям и там тоже). В случае с Почтой для доменов можно даже не настраивать MX, достаточно только подтвердить владение. После этого владение автоматически подтвердится и в Почтовом офисе.
  • 0
    Статья написана год назад, а я только что увидел лог в хроме (скрин):
    Refused to load the image 'https://yandexadexchange.net/claim?k=5776172222795026221&d=h' because it violates the following Content Security Policy directive: «img-src 'self' data: *.gemius.pl *.tns-counter.ru maps.googleapis.com *.yandex.ru *.yandex.net *.yandex.ru view.atdmt.com *.doubleclick.net lamoda25.ru bs.serving-sys.com r24-tech.com yandex.st yastatic.net».

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

Самое читаемое Разработка