Главная » Хабрахабр » Безопасность мобильного OAuth 2.0

Безопасность мобильного OAuth 2.0

Я Никита Ступин, специалист по информационной безопасности Почты Mail. Всем привет! Не так давно я провел исследование уязвимостей мобильного OAuth 2. Ru. Для создания безопасной схемы мобильного OAuth 2. 0. Необходимо учитывать специфику мобильных приложений и применять дополнительные механизмы защиты. 0 мало реализовать стандарт в чистом виде и проверять redirect_uri.

0, о методах защиты и безопасной реализации этого протокола. В этой статье я хочу поделиться с вами знаниями об атаках на мобильный OAuth 2. Ru.
Все необходимые компоненты защиты, о которых я расскажу ниже, реализованы в последней версии SDK для мобильных клиентов Почты Mail.

OAuth 2.0 — это протокол авторизации, который описывает, как сервису-клиенту безопасно получить доступ к ресурсам пользователя на сервисе-провайдере. При этом OAuth 2.0 избавляет пользователя от необходимости вводить пароль за пределами сервиса-провайдера: весь процесс сводится к нажатию кнопки «Согласен предоставить доступ к ...».

0 — это сервис, который владеет данными пользователя и, с разрешения пользователя, предоставляет сторонним сервисам (клиентам) безопасный доступ к этим данным. Провайдер в терминах OAuth 2. Клиент — это приложение, которое хочет получить данные пользователя, находящиеся у провайдера.

0 обычные разработчики приспособили его для аутентификации, хотя изначально он для этого не предназначался. Через некоторое время после релиза протокола OAuth 2. Аутентификация смещает вектор атаки с данных пользователя, которые хранятся у сервиса-провайдера, на аккаунты пользователей сервиса-клиента.

В эру мобильных приложений и превознесения конверсии вход в приложение при помощи одной кнопки стал очень заманчивым. Одной лишь аутентификацией дело не ограничилось. 0 на мобильные рельсы. Разработчики поставили OAuth 2. Впрочем, OAuth 2. Естественно, мало кто задумывался о безопасности и специфике мобильных приложений: раз-раз, и в продакшн. 0 вообще плохо работает за пределами веб-приложений: одни и те же проблемы наблюдаются и в мобильных, и в десктопных приложениях.

0. Давайте разберемся, как же всё-таки сделать безопасный мобильный OAuth 2.

Помните, что на мобильных устройствах в роли клиента может выступать не браузер, а мобильное приложение без бэкенда. Поэтому мы сталкиваемся с двумя основными проблемами безопасности мобильного OAuth 2.0:

  1. Клиент не является доверенным.
  2. Поведение редиректа из браузера в мобильное приложение зависит от настроек и приложений, которые установил пользователь.

Мобильное приложение — это публичный клиент

Чтобы понять корни первой проблемы, давайте посмотрим, как работает OAuth 2.0 в случае взаимодействия server-to-server, а затем сравним его с OAuth 2.0 в случае взаимодействия client-to-server.

Значение client_id является публичным и необходимо для идентификации сервиса-клиента, в отличие от client_secret, значение которого является приватным. В обоих случаях всё начинается с того, что сервис-клиент регистрируется у сервиса-провайдера и получает client_id и, в некоторых случаях, client_secret. Более подробно процесс регистрации описан в RFC 7591.

0 при взаимодействии server-to-server. На схеме ниже показана работа OAuth 2.

2
Картинка взята из https://tools.ietf.org/html/rfc6749#section-1.

0: Можно выделить 3 основных этапа протокола OAuth 2.

  1. [шаги A-C] Получить Authorization Code (далее просто code).
  2. [шаги D-E] Обменять code на access_token.
  3. Получить доступ к ресурсу с помощью access_token.

Разберем получение code подробнее:

  1. [Шаг A] Сервис-клиент перенаправляет пользователя на сервис-провайдер.
  2. [Шаг B] Сервис-провайдер запрашивает у пользователя разрешение на предоставление данных сервису-клиенту (стрелка B вверх). Пользователь предоставляет доступ к данным (стрелка B вправо).
  3. [Шаг C] Сервис-провайдер возвращает code браузеру пользователя, а тот перенаправляет code сервису-клиенту.

Разберем получение access_token подробнее:

  1. [Шаг D] Сервер клиента отправляет запрос на получение access_token. В запрос включаются: code, client_secret и redirect_uri.
  2. [Шаг E] В случае валидных code, client_secret и redirect_uri предоставляется access_token.

Запрос за access_token выполняется по схеме server-to-server, поэтому в общем случае для похищения client_secret злоумышленник должен взломать сервер сервиса-клиента или сервер сервиса-провайдера.

0 на мобильном устройстве без бэкенда (взаимодействие client-to-server). Теперь посмотрим, как выглядит схема OAuth 2.

1
Картинка взята из https://tools.ietf.org/html/rfc8252#section-4.

Общая схема разбивается на те же 3 основных шага:

  1. [шаги 1-4 на картинке] Получить code.
  2. [шаги 5-6 на картинке] Обменять code на access_token.
  3. Получить доступ к ресурсу с помощью access_token.

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

Казалось бы, зачем нам лишний шаг? Относительно схемы client-to-server у вас мог возникнуть вопрос: «а почему бы сразу не получить access_token?». И хотя в некоторых случаях её использовать можно, ниже мы увидим, что для безопасного мобильного OAuth 2. Более того, существует схема Implicit Grant, при которой клиент сразу получает access_token. 0 схема Implicit Grant не подходит.

Редирект на мобильных устройствах

В общем случае, для редиректа из браузера в приложение на мобильных устройствах используются механизмы Custom URI Scheme и AppLink. Ни один из этих механизмов в чистом виде не является столь же надежным, как браузерный редирект.

Схема может быть произвольной, при этом на одном устройстве может быть установлено несколько приложений с одинаковой схемой. Custom URI Scheme (или deep link) используется следующим образом: разработчик перед сборкой определяет схему приложения. А что если два приложения зарегистрировали одинаковую схему на одном устройстве? Всё довольно просто, когда на устройстве каждой схеме соответствует одно приложение. Android покажет окно с выбором приложения, в котором нужно открыть ссылку. Как операционной системе определить, какое из двух приложений открыть при обращении по Custom URI Scheme? В обоих случаях у злоумышленника появляется возможность перехватить code или access_token. В iOS поведение не определено, а значит может быть открыто любое из двух приложений.

AppLink, в противоположность Custom URI Scheme, позволяет гарантированно открыть нужное приложение, но у этого механизма есть ряд недостатков:

  1. Каждый сервис-клиент должен самостоятельно проходить процедуру верификации.
  2. Пользователи Android могут выключить AppLink для конкретного приложения в настройках.
  3. Android ниже 6.0 и iOS ниже 9.0 не поддерживают AppLink.

Вышеуказанные недостатки AppLink, во-первых, повышают порог вхождения для потенциальных сервисов-клиентов, а во-вторых, могут привести к тому, что при определенных обстоятельствах у пользователя не будет работать OAuth 2.0. Это делает механизм AppLink непригодным для замены браузерным редиректам в протоколе OAuth 2.0.
Проблемы мобильного OAuth 2.0 породили и специфические атаки. Давайте разберемся, что они собой представляют и как работают.

Authorization Code Interception Attack

Исходные данные: на устройстве пользователя установлено легитимное приложение (клиент OAuth 2.0) и зловредное приложение, которое зарегистрировало ту же схему, что и легитимное. На рисунке ниже приведена схема атаки.


Картинка взята из https://tools.ietf.org/html/rfc7636#section-1

После этого зловред меняет code на access_token и получает доступ к данным пользователя. Проблема здесь вот в чем: на шаге 4 браузер возвращает code в приложение через Custom URI Scheme, поэтому code может быть перехвачен зловредом (потому что он зарегистрировал ту же схему, что и легитимное приложение).

В некоторых случаях можно использовать механизмы межпроцессного взаимодействия, о них мы поговорим ниже. Как защититься? Суть ее отражена на схеме ниже. В общем же случае необходимо применять схему, которая называется Proof Key for Code Exchange.

1
Картинка взята из https://tools.ietf.org/html/rfc7636#section-1.

В запросах от клиента есть несколько дополнительных параметров: code_verifier, code_challenge (на схеме t(code_verifier)) и code_challenge_method (на схеме t_m).

То есть для каждого запроса на получение code клиент должен генерировать новый code_verifier. Code_verifier — это случайное число длиной минимум 256 бит, которое используется только один раз.

Code_challenge_method — это название функции преобразования, чаще всего SHA-256.

Code_challenge — это code_verifier, к которому применили преобразование code_challenge_method и закодировали в URL Safe Base64.

Преобразование code_verifier в code_challenge необходимо, чтобы защититься от векторов атак, основанных на перехвате code_verifier (например, из системных логов устройства) при запросе code.

Во всех остальных случаях необходимо использовать SHA-256. В случае, если устройство пользователя не поддерживает SHA-256, то допустим даунгрейд до отсутствия преобразования code_verifier.

Работает схема следующим образом:

  1. Клиент генерирует code_verifier и запоминает его.
  2. Клиент выбирает code_challenge_method и получает code_challenge из code_verifier.
  3. [Шаг А] Клиент запрашивает code, причем в запрос добавляется code_challenge и code_challenge_method.
  4. [Шаг Б] Провайдер запоминает code_challenge и code_challenge_method на сервере и возвращает code клиенту.
  5. [Шаг C] Клиент запрашивает access_token, причем в запрос добавляется code_verifier.
  6. Провайдер получает code_challenge из пришедшего code_verifier, а затем сверяет его с code_challenge, который он запомнил.
  7. [Шаг D] Если значения совпадают, то провайдер выдает клиенту access_token.

Давайте разберемся, почему code_challenge позволяет защититься от атаки перехвата кода. Для этого пройдем по этапам получения access_token.

  1. Сначала легитимное приложение запрашивает code (вместе с запросом пересылается code_challenge и code_challenge_method).
  2. Зловред перехватывает code (но не code_challenge, потому что в ответе code_challenge отсутствует).
  3. Зловред запрашивает access_token (с валидным code, но без валидного code_verifier).
  4. Сервер замечает несоответствие code_challenge и выдает ошибку.

Заметьте, что у злоумышленника нет возможности угадать code_verifier (рандомные 256 бит!) или найти его где-то в логах (code_verifier передается один раз).

Если свести всё это в одну фразу, то code_challenge позволяет ответить сервису-провайдеру на вопрос: «access_token запрашивается тем же приложением-клиентом, которое запросило code, или другим?».

OAuth 2.0 CSRF

На мобильных устройствах OAuth 2.0 зачастую используется в качестве механизма аутентификации. Как мы помним, аутентификация через OAuth 2.0 отличается от авторизации тем, что уязвимости OAuth 2.0 затрагивают данные пользователя на стороне сервиса-клиента, а не сервиса-провайдера. В результате CSRF-атака на OAuth 2.0 позволяет украсть чужой аккаунт.

0 на примере приложения-клиента taxi и провайдера provider.com. Рассмотрим CSRF-атаку применительно к OAuth 2. После этого злоумышленник прерывает процесс OAuth 2. Сначала злоумышленник на своем устройстве входит в аккаунт attacker@provider.com и получает code для taxi. 0 и генерирует ссылку:

Жертва переходит по ссылке, на её телефоне открывается приложение taxi, которое получает access_token, и в результате жертва попадает в taxi-аккаунт злоумышленника. com.taxi.app://oauth?
code=b57b236c9bcd2a61fcd627b69ae2d7a6eb5bc13f2dc25311348ee08df43bc0c4

Затем злоумышленник отправляет ссылку жертве, например, под видом письма или SMS от администрации taxi. Не ведая подвоха, жертва пользуется этим аккаунтом: совершает поездки, вводит свои данные и т.д.

CSRF-атака на логин позволила украсть аккаунт. Теперь злоумышленник может в любое время зайти в taxi-аккаунт жертвы, потому что он привязан к attacker@provider.com.

0 не исключение. От CSRF-атак обычно защищаются с помощью CSRF-токена (также его называют state), и OAuth 2. Как использовать CSRF-токен:

  1. Приложение-клиент генерирует и сохраняет CSRF-токен на мобильном устройстве пользователя.
  2. Приложение-клиент включает CSRF-токен в запрос на получение code.
  3. Сервер возвращает в ответе вместе с code тот же самый CSRF-токен.
  4. Приложение-клиент сравнивает пришедший и сохраненный CSRF-токен. Если значения совпадают, то процесс продолжается дальше.

Требования к CSRF-токену: nonce длиной минимум 256 бит, полученный из хорошего источника псевдослучайных последовательностей.

Если коротко, то CSRF-токен позволяет приложению-клиенту ответить на вопрос: «это я начал получение access_token, или кто-то пытается меня обмануть?».

Зловред, притворяющийся легитимным клиентом

Некоторые зловреды могут мимикрировать под легитимные приложения и поднимать consent screen от их имени (consent screen — это экран, на котором пользователь видит: «Согласен предоставить доступ к ...»). Невнимательный пользователь может нажать «разрешить», и в результате зловред получает доступ к данным пользователя.

Приложение-провайдер может убедиться в легитимности приложения-клиента, и наоборот. Android и iOS предоставляют механизмы взаимной проверки приложений.

0 использует поток через браузер, то защититься от этой атаки нельзя. К сожалению, если механизм OAuth 2.

Другие атаки

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

Хороший, плохой OAuth 2.0

Начнем с того, как правильно поднимать consent screen. На мобильных устройствах существует два способа открыть веб-страницу из нативного приложения (примеры нативных приложений: Почта Mail.Ru, VK, Facebook).

Примечание: Browser Custom Tab на Android называется Chrome Custom Tab, а на iOS SafariViewController. Первый способ называется Browser Custom Tab (на картинке слева). не происходит визуального переключения между приложениями. По сути, это обычная вкладка браузера, которая отображается прямо в приложении, т.е.

0 я считаю его плохим. Второй способ называется «поднять WebView» (на картинке справа), применительно к мобильному OAuth 2.

WebView — это обособленный браузер для нативного приложения.

Обратное утверждение тоже верно: Safari и Chrome не могут получить доступ к данным WebView. «Обособленный браузер» означает, что для WebView запрещен доступ к кукам, хранилищу, кешу, истории и другим данным браузеров Safari и Chrome.

«Браузер для нативного приложения» означает, что нативное приложение, которое подняло WebView, имеет полный доступ к кукам, хранилищу, кешу, истории и другим данным WebView.

А теперь представьте: пользователь нажимает кнопку «войти с помощью ...» и WebView зловредного приложения запрашивает у него логин и пароль от сервиса-провайдера.

Провал сразу по всем фронтам:

  1. Пользователь вводит логин и пароль от аккаунта сервиса-провайдера в приложении, которое легко может похитить эти данные.
  2. OAuth 2.0 изначально разрабатывался для того, чтобы не вводить логин и пароль от сервиса-провайдера.
  3. Пользователь привыкает вводить логин и пароль где попало, увеличивается вероятность фишинга.

Учитывая, что все аргументы против WebView, вывод напрашивается сам: поднимайте Browser Custom Tab для consent screen.

Если у кого-то из вас есть аргументы в пользу WebView вместо Browser Custom Tab, напишите об этом в комментариях, я буду очень благодарен.

Безопасная схема мобильного OAuth 2.0

Мы будем использовать схему Authorization Code Grant, потому что она позволяет добавить code_challenge и защититься от атаки перехвата кода.

1
Картинка взята из https://tools.ietf.org/html/rfc8252#section-4.

Запрос на получение code (шаги 1-2) будет выглядеть следующим образом:

https://o2.mail.ru/code?
redirect_uri=com.mail.cloud.app%3A%2F%2Foauth&
anti_csrf=927489cb2fcdb32e302713f6a720397868b71dd2128c734181983f367d622c24& code_challenge=ZjYxNzQ4ZjI4YjdkNWRmZjg4MWQ1N2FkZjQzNGVkODE1YTRhNjViNjJjMGY5MGJjNzdiOGEzMDU2ZjE3NGFiYw%3D%3D&
code_challenge_method=S256&
scope=email%2Cid&
response_type=code&
client_id=984a644ec3b56d32b0404777e1eb73390c

На шаге 3 браузер получает ответ с редиректом:

com.mail.cloud.app://oаuth?
code=b57b236c9bcd2a61fcd627b69ae2d7a6eb5bc13f2dc25311348ee08df43bc0c4&
anti_csrf=927489cb2fcdb32e302713f6a720397868b71dd2128c734181983f367d622c24

На шаге 4 браузер открывает Custom URI Scheme и передает code и CSRF-токен в клиентское приложение.

Запрос на получение access_token (шаг 5):

https://o2.mail.ru/token?
code_verifier=e61748f28b7d5daf881d571df434ed815a4a65b62c0f90bc77b8a3056f174abc&
code=b57b236c9bcd2a61fcd627b69ae2d7a6eb5bc13f2dc25311348ee08df43bc0c4&
client_id=984a644ec3b56d32b0404777e1eb73390c

На последнем шаге возвращается ответ с access_token.

0 можно сделать проще и чуть-чуть безопаснее. В общем случае вышеприведённая схема является безопасной, но существуют и частные случаи, в которых OAuth 2.

Android IPC

В Android существует механизм двустороннего обмена данными между процессами: IPC (inter-process communication). IPC предпочтительнее Custom URI Scheme по двум причинам:

  1. Приложение, которое открывает IPC-канал, может проверить подлинность открываемого приложения по его сертификату. Верно и обратное: открытое приложение может проверить подлинность приложения, которое его открыло.
  2. Отправив запрос через IPC-канал, отправитель может получить ответ через этот же канал. Вкупе со взаимной проверкой (п.1) это означает, что никакой сторонний процесс не сможет перехватить access_token.

0. Таким образом, мы можем использовать Implicit Grant и значительно упростить схему мобильного OAuth 2. Более того, мы сможем защититься от зловредов, которые мимикрируют под валидные клиенты с целью кражи аккаунтов пользователя. Никаких code_challenge и CSRF-токенов.

SDK для клиентов

Помимо реализации безопасной схемы мобильного OAuth 2.0, приведенной выше, провайдеру следует разработать SDK для своих клиентов. Это облегчит внедрение OAuth 2.0 на стороне клиента и одновременно сократит количество ошибок и уязвимостей.
Для провайдеров OAuth 2.0 я составил «Чеклист безопасного мобильного OAuth 2.0»:

  1. Прочный фундамент жизненно важен. В случае с мобильным OAuth 2.0 фундаментом является схема или протокол, который мы выберем для реализации. При реализации собственной схемы OAuth 2.0 легко ошибиться. Другие уже набили шишки и сделали выводы, нет ничего зазорного в том, чтобы учиться на их ошибках и сразу сделать безопасную реализацию. В общем случае самой безопасной схемой мобильного OAuth 2.0 является схема из раздела «Что делать-то?».
  2. Access_token и другие чувствительные данные храните: под iOS — в Keychain, под Android — в Internal Storage. Эти хранилища специально разработаны для таких целей. В случае необходимости в Android можно использовать Content Provider, но его нужно безопасно настроить.
  3. Code должен быть одноразовый, с коротким временем жизни.
  4. Для защиты от перехвата code используйте code_challenge.
  5. Для защиты от CSRF-атаки на логин используйте CSRF-токены.
  6. Не используйте WebView для consent screen, используйте Browser Custom Tab.
  7. Client_secret бесполезен, если он не хранится на бэкенде. Не выдавайте его публичным клиентам.
  8. Используйте HTTPS везде, с запретом даунгрейда до HTTP.
  9. Следуйте рекомендациям по криптографии (выбор шифра, длина токена и т.д.) из стандартов. Можете скопировать данные и разобраться, почему сделано именно так, но делать свою криптографию нельзя.
  10. Со стороны приложения-клиента проверяйте, кого вы открываете для OAuth 2.0, а со стороны приложения-провайдера проверяйте, кто вас открывает для OAuth 2.0.
  11. Помните об обычных уязвимостях OAuth 2.0. Мобильный OAuth 2.0 расширяет и дополняет обычный, поэтому никто не отменял проверку redirect_uri на точное совпадение и прочие рекомендации для обычного OAuth 2.0.
  12. Обязательно предоставляйте клиентам SDK. У клиента будет меньше ошибок и уязвимостей в коде, и ему будет проще внедрить ваш OAuth 2.0.

  1. [RFC] OAuth 2.0 for Native Apps https://tools.ietf.org/html/rfc8252
  2. Google OAuth 2.0 for Mobile & Desktop Apps https://developers.google.com/identity/protocols/OAuth2InstalledApp
  3. [RFC] Proof Key for Code Exchange by OAuth Public Clients https://tools.ietf.org/html/rfc7636
  4. OAuth 2.0 Race Condition https://hackerone.com/reports/55140
  5. [RFC] OAuth 2.0 Threat Model and Security Considerations https://tools.ietf.org/html/rfc6819
  6. Атаки на обычный OAuth 2.0 https://sakurity.com/oauth
  7. [RFC] OAuth 2.0 Dynamic Client Registration Protocol https://tools.ietf.org/html/rfc7591

Спасибо всем, кто помог написать эту статью, особенно Сергею Белову, Андрею Сумину, Андрею Лабунцу (@isciurus) и Дарье Яковлевой.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

[Перевод] Учебный курс по React, часть 1: обзор курса, причины популярности React, ReactDOM и JSX

Представляем вашему вниманию первые 5 занятий учебного курса по React для начинающих. Оригинал курса на английском, состоящий из 48 уроков, опубликован на платформе Scrimba.com. Возможности этой платформы позволяют, слушая ведущего, иногда ставить воспроизведение на паузу и самостоятельно, в том же ...

[Перевод] Разбираем лямбда-выражения в Java

Мы открыли его для себя совсем недавно, но уже по достоинству оценили его возможности. От переводчика: LambdaMetafactory, пожалуй, один из самых недооценённых механизмов Java 8. 0 фреймворка CUBA улучшена производительность за счет отказа от рефлективных вызовов в пользу генерации лямбда ...