Как стать автором
Обновить

Комментарии 25

Есть браузеры которые custom uri schema не поддерживают: они не открывают приложение, подписанное на такие схемы. link

К сожалению, они могут быть достаточно популярны. Что делать в этом случае?
В приведенном тобой кейсе человек зарегистрировал за своим приложением схему «http», поэтому нет ничего удивительного, что некоторые браузеры не открывают такое приложение.

В статье же речь идет о кастомной схеме (например, mailrumail), которая браузеру неизвестна.

Я не сталкивался с ситуациями, когда браузеры полностью не поддерживают Custom URI Scheme. Если такие есть, можешь привести пример?
Не совсем правильную ссылку нашел. Кажется вот эта вернее:
bugs.chromium.org/p/chromium/issues/detail?id=170880

Тестирования в двух компаниях где я работал стабильно находило этот баг:(
В этом кейсе бага была в браузере: он должен открывать Custom URI Scheme, но не делает этого. В таком случае нужно поступать как и со всеми багами — исправлять их. Всегда есть риск того, что на более низком уровене (браузер, операционная система, драйвера) будут баги или уязвимости, которые затронут работу приложения.

Другое дело, если найдется браузер, для которого не открыть Custom URI Scheme — ожидаемое поведение, и при этом он будет достаточно популярным. В таком случае нужно задуматься путях решения проблемы или об альтернативах Custom URI Scheme.
Пара вопросов:
1) Зачем нам добиваться редиректа в приложение? Почему не подходит вариант открывать вебвью для прохождения веб-части авторизации в рамках самого своего приложения и просто доставать полученный authorization_code из него через интерфейс взаимодействия с вебвью? В этом случае браузер и приложение существуют рядом друг с другом и связываться с кастомными схемами просто не требуется.
2) Если обращение в Token Endpoint из вашего приложения проксировать через свой сервер, то хранение client_secret можно перенести на него. Равно как и избавиться от передачи authorization_code в приложение — можно сразу за счёт указания redirect_url передавать его на свой бэкенд и в ответ возвращать в браузер готовый access_token. Стоят ли все эти ухищрения в вашей статье того, чтобы сэкономить не так много времени на реализацию простого бэкенда для авторизации? Совсем server-less приложения это всё же редкость…
Ответ на первый вопрос. Приложение, которое подняло WebView, может получить доступ ко всем данным WebView, в том числе: логину/паролю, сессионным токенам пользователя. Если приложение легитимное и «доброе», то все хорошо. А вот если WebView поднимет зловред, то он легко может сфишить пользователя. Более подробно про этот и другие аргументы против WebView можно почитать в разделе «Хороший, плохой OAuth 2.0» этой статьи.

По второму вопросу. У приложения может быть серверная часть, но кому-то дешевле оставить взаимодействие с токенами на стороне мобильного приложения. Не нужны сервера для хранения токенов и обработки запросов, не нужна разработка и тестирование серверного кода, не нужно заботиться о безопасности такой инфраструктуры. Это не отменяет тех случаев, когда OAuth 2.0 клиенты получают, используют и хранят access_token на сервере, а не в мобильном приложении. Оба варианта возможны и какой вариант использовать — это по большей части выбор OAuth 2.0 клиента (при условии, что OAuth 2.0 провайдер безопасно реализовал оба варианта на своей стороне).
Но ведь если начинать рассуждать о зловредных приложениях, то почему мы не рассматриваем вариант с нехорошим браузером, в котором открываем наш consent screen? Это может быть и не chrome, и что-то косящее под chrome, но точно так же способное сфишить логин и пароль.
Вообще, если говорить про легитимность приложений, то инструмент, дающий возможность на сервере отделить своих от чужих был бы очень кстати. В частности, мы могли бы просто не открывать в WebView страницу ввода учётных данных вообще, если знаем, что инициатором запроса было не наше приложение (если, конечно, провайдером OAuth2 являемся мы сами). И, как я понимаю, это уже возможно через использование Attestation API (https://developer.android.com/training/safetynet/attestation).
На Android приложение может проверить какой браузер оно открывает и разрешать только браузеры из белого списка. В нашем OAuth 2.0 SDK для клиентов такая функция есть, я не указал ее в статье. На iOS кастомные браузеры все равно используют Safari, поэтому проблем с зловредными браузерами там нет.

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

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

Если приложение-клиент использует OAuth 2.0 через приложение-провайдер, то такая проверка возможна. Если же приложение-клиент использует OAuth 2.0 через браузер, то там обычный HTTP запрос и зловред может его подделать, потому что секреты в мобильном приложении безопасно не зашить (их можно проснифать или разреверсить).

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

Вот не надо так! Откуда возьмутся такие устройства, на которых реально нет возможности реализовать SHA-256, но при этом есть возможность запускать свои OAuth-клиенты? Большинство проблем с безопасностью OAuth вызвано тем, что критичный для безопасности функционал объявляют опциональным, ибо "это фреймворк".


Приложение-клиент сравнивает пришедший и сохраненный CSRF-токен. Если значения совпадают, то процесс продолжается дальше.

Не всё так просто. Бывают атаки, которые намеренно искажают state отправляемый или получаемый легитимным клиентом (не уверен что мобильные, но для веб-клиентов я про такое читал) — делается это как раз для того, чтобы легитимный клиент остановился на этом этапе. Если state не совпал, то клиент всё-равно должен послать запрос к token endpoint и передать туда code; а вот уже после получения ответа от token endpoint (любого, включая ошибку и корректный access_token) клиент должен прервать процесс (и выкинуть полученный access_token, даже если он его получил) по причине некорректного state.


Вообще, в такие статьи надо звать Homakov, он много забавного может рассказать (если не путаю, то "OAuth by Sakurity" на который ссылается статья это его, либо основан на его "OAuth Security Cheatsheet"). :)


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

Из описания не совсем понятен сценарий атаки, можете пояснить как конкретно это работает?


Таким образом, мы можем использовать Implicit Grant и значительно упростить схему мобильного OAuth 2.0.

А разве использование IPC вместо HTTP не подразумевает автоматически, что это уже вообще не OAuth и не Implicit Grant, а просто какой-то свой простейший RPC-протокол access_token,err:=authorize(scope) (client_id уже известен из взаимной проверки при установлении IPC, redirect_uri не имеет смысла — хотя по протоколу OAuth это обязательное поле, если не путаю).

Откуда возьмутся такие устройства, на которых реально нет возможности реализовать SHA-256, но при этом есть возможность запускать свои OAuth-клиенты?

Подобные устройства я не встречал и у меня возник точно такой же вопрос при чтении стандарта. Думаю, что в стандарте не просто так сделали примечание про устройства без поддержки SHA-256, поэтому и пишу про это в статье.

Еще раз подчеркну, что если SHA-256 поддерживается, то нельзя делать даунгрейд до отсутствия преобразования code_verifier. Это указано и в статье, и в RFC 7636.

Бывают атаки, которые намеренно искажают state отправляемый или получаемый легитимным клиентом (не уверен что мобильные, но для веб-клиентов я про такое читал) — делается это как раз для того, чтобы легитимный клиент остановился на этом этапе



Интересно, не знал про подобные атаки. А для чего злоумышленнику останавливать клиента на этом этапе?

Из описания не совсем понятен сценарий атаки, можете пояснить как конкретно это работает?

У пользователя установлены два приложения: легитимное и зловред. Происходит следующее:
  1. Зловред поднимает consent screen легитимного приложения. Для этого зловреду достаточно передать client_id легитимного клиента.
  2. Невнимательный пользователь может разрешить доступ к своим ресурсам и подумать, что дает доступ легитимному приложению (на consent screen ведь иконка и название легитимного приложения), а в действительности он предоставит доступ зловреду.
  3. После этого зловред получит code, обменяет его на access_token и получит доступ к ресурсам пользователя.


А разве использование IPC вместо HTTP не подразумевает автоматически, что это уже вообще не OAuth и не Implicit Grant, а просто какой-то свой простейший RPC-протокол 

На мой взгляд, IPC это всего лишь транспорт (способ доставки) access_token, который заменяет HTTP. Протокол OAuth 2.0 показан на примере транспорта HTTP, потому что он наиболее популярен в вебе. Если посмотреть на описание Implicit Grant, то видно, что там не сказано, что транспортом обязательно должен быть HTTP.

То что IPC из коробки может передать client_id и "redirect_uri" и по тому же соединению получить ответ — не делает его хуже HTTP.

При использованием новых транспортов самое главное — учесть все возможные проблемы безопасности, связанные с транспортом. Хороший пример — кейс с использованием postMessage в реализации OAuth 2.0. Подчеркну, что сам по себе postMessage не является плохим транспортом, но необходимо учитывать возможные проблемы безопасности, которые он привносит.
А для чего злоумышленнику останавливать клиента на этом этапе?

Злоумышленник перехватил code (одновременно с искажением state), и хочет получить access_token. Для этого ему нужно послать запрос на token endpoint. Если запрос пошлёт только он — для сервера OAuth это будет выглядеть легитимным запросом. Если запрос пошлёт и он, и настоящий клиент — сервер сможет определить вероятную утечку code по факту получения двух запросов с одинаковым code, и при получении второго из них инвалидировать выданный в ответ на первый запрос access_token. Таким образом, "окно", в течении которого злоумышленник может использовать access_token сужается до долей секунды, плюс сервер получает информацию о самом факте утечки, что тоже ценно.


У пользователя установлены два приложения: легитимное и зловред.

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


Если посмотреть на описание Implicit Grant, то видно, что там не сказано, что транспортом обязательно должен быть HTTP.

Хм. Насколько я его понимаю — как раз сказано (выделенные термины подразумевают именно HTTP):


"Since this is a redirection-based flow, the client must be capable of interacting with the resource owner's user-agent (typically a web browser) and capable of receiving incoming requests (via redirection) from the authorization server."

Ну и плюс к этому в разделе Introduction написано вполне однозначно:


"This specification is designed for use with HTTP ([RFC2616]). The use of OAuth over any protocol other than HTTP is out of scope."
Злоумышленник перехватил code (одновременно с искажением state), и хочет получить access_token.




А в каком случае злоумышленник может перехватить code и исказить state, но при этом не может просто прервать запрос пользователя? Я вижу вариант, когда злоумышленник MitM-ит пользователя, но в этом случае он может и другими способами прервать запрос за access_token.

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




Да, все так. Настоящего клиента может и не быть, суть атаки именно в том, что зловред показывает чужой consent screen, а пользователь из-за невнимательности фишится. Вектор атаки не супер критичный, но все равно я думаю, что лучше его учесть и принять меры защиты.

«This specification is designed for use with HTTP ([RFC2616]). The use of OAuth over any protocol other than HTTP is out of scope.»




Это можно трактовать как «OAuth 2.0 должен использоваться только вместе с HTTP», так и как «OAuth 2.0 разрабатывался для HTTP, любые другие транспорты вы используете на свой страх и риск». Я больше склоняюсь ко второй. При использовании транспортов отличных от HTTP нужно учитывать уязвимости, которые транспорт привнесет вместе с собой, и тщательно проверять на безопасность. Называть получившийся после замены транспорта протокол OAuth-ом или не называть, на мой взгляд, уже холиварный вопрос.
А в каком случае злоумышленник может перехватить code и исказить state, но при этом не может просто прервать запрос пользователя?

Я проверил свою шпаргалку по безопасности OAuth: к сожалению, я там не сохранил ссылку на конкретное описание атаки, но у меня это описано в разделе "если OAuth клиент уязвим к XSS". XSS бывают очень разные, не всегда есть возможность полностью захватить контроль над скриптами уязвимого сайта чтобы помешать им использовать code, но вполне можно исказить значение state, сохранённое где-то между отправкой запроса на authorization endpoint и редиректом обратно. Насколько это реальный кейс и насколько он применим к мобильным клиентам — мне сложно сказать, я всё-таки не эксперт в области безопасности, скорее просто продвинутый любитель. :)

На момент публикации RFC 6759 Oauth 2.0 использовался для HTTP. В настоящее время, существуют стандарты адаптирующие OAuth для использования в протоколах отличных от HTTP, например RFC 7628 — совместно с SASL (используется в почтовых протоколах).
А теперь представьте: пользователь нажимает кнопку «войти с помощью ...» и WebView зловредного приложения запрашивает у него логин и пароль от сервиса-провайдера.

Я не вполне понял этот сценарий — можете уточнить?


Пользователь в моем (легитимном) приложении нажимает кнопку "Войти", открывается WebView, к которому мое же приложение имеет полный доступ.
Что тут плохого? На каком этапе доступ к этому WebView получает приложение-зловред?

В этом сценарии легитимное приложение не участвует. Зловредное приложение регистрируется как обычный OAuth 2.0 клиент (например, у Google можно легко зарегистрировать свой OAuth 2.0 клиент через веб-интерфейс), может быть даже какое-то время работает в нормальном режиме и не приносит вреда, но при этом оно всегда имеет возможность прочитать логин и пароль пользователя, если тот авторизуется по OAuth 2.0 через WebView (WebView будет поднято в зловредном приложении, но с точки зрения SDK это обычный OAuth 2.0 клиент).

Так может не стоит приучать пользователей вводить пароль в приложении, ведь, как я понял, "на глаз" пользователь небезопасный WebView от безопасного Browser Custom Tab отличить может с большим трудом, а вот переключение в системный браузер всё-таки намного заметнее и понятнее?

А зачем? Пользователь не должен (и вряд ли будет) искать глазами и разбираться что перед ним: WebView или Browser Custom Tab. Смысл использования Browser Custom Tab в том, что он безопаснее и может подхватить уже существующую сессию пользователя.

Затем, что кто помешает стороннему клиенту в какой-то момент заменить в своём мобильном приложении Browser Custom Tab на WebView и начать красть пароли юзеров, причём так, что никто этого даже и не заметит?


По хорошему, нужен какой-то механизм определения WebView на стороне OAuth сервера. Что-то аналогичное тому, как сейчас мы определяем (и запрещаем соответствующими HTTP заголовками) встраивать страничку consent в фреймы на сторонних сайтах. Это бы дало возможность тупо заблокировать использование WebView любыми мобильными клиентами.

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

Я хочу приучить вводить пароль только в одном приложении — в браузере, в котором пользователи более-менее научились отличать настоящие сайты от зловредных (на самом деле нет, но там их хотя бы этому постоянно учат и дают какие-то способы их отличать). Потому что, во-первых, мне не очень нравится ставить на телефон лишние (потому что я ими не пользуюсь) "официальные" приложения только ради того, чтобы они показывали запрос логина/пароля, и во-вторых, как описано в статье, пользователю очень сложно определить какое именно мобильное приложение запрашивает пароль — легитимное или зловредное.

То есть в статье рекомендация про WebView — это рекомендация злоумышленнику по написанию более безопасного зловреда?

Конечно же нет. Рекомендация про WebView направлена на то, чтобы максимально обезопасить пользователя и облегчить ему вход в приложение. Если у него есть сессия в браузере, то эта сессия сразу подхватится, соответственно никаких данных пользователь вводить не будет. В случае с WebView пользователю всегда придется вводить логин и пароль, следовательно повышается вероятность, что пользователь когда-то введет логин и пароль в зловредном приложении.

Спасибо, теперь понял.


Безотносительно к безопасности, решение, которое требует от юзера ввод пароля каждый раз, убивает на корню все прелести OAuth. Это просто не имеет смысла. Наверное, это и сбило меня с толку.

К сожалею, в статье не рассмотрены некоторые фундаментальные аспекты безопасности OAuth, которые я осветил тут: habr.com/ru/post/320744
В частности, не говорится о том, что в общем случае невозможно отличать сервисы-клиенты по client_id. Соответственно в общем случае нельзя выдавать чувствителеные к client_id методы в публичный API.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.