Info Security
0,0
рейтинг
6 сентября 2012 в 01:43

Разработка → Безопасность OAuth2

Данная блогозапись на хабр прежде всего обусловлена появлением «Ключницы» — хороший повод связать и перевести накопленное.


У нас в программе: вольный пересказ спек OAuth2, слабые стороны и Threat Model, 0day на хабретрюк с аутенфикацией.
image

OAuth2 это просто


Длинный, но правильный путь изучить его.
Для ленивых я опишу своими словами Authorization Code Flow как самый распространенный и безопасный, aka Server-Side.

Ключевые понятия — Клиент(website/online game/app), Юзер(you), Провайдер(facebook/vkontakte/google), Код(code) и Токен(access_token).

Клиент посылает Юзера — «авторизуй меня для доступа к твоим ресурсам». Юзер идет по ссылке к провайдеру, смотрит что от него требуют — scope параметр — жмет Разрешить. Далее Провайдер редиректит его назад на указанный redirect_uri на домене Клиента со следующими параметрами:
code — идентификатор Юзера у Провайдера, который нужен Клиенту чтобы получить Токен
state — то же значение что было передано на начальный URL. используется для защиты от CSRF и для удобства.

Код из себя не представляет никакой ценности для Юзера(и User-Agent соответственно) т.к. с его помощью нельзя совершать запросы API и нужен он лишь для одной цели — получения Токена.
Для получения Токена Клиент производит запрос на определенный эндпоинт, передав client_id, client_secret, code, redirect_uri по которому был получен код — таким образом Провайдер уверен что это нужный Клиент и по Коду он отдает Токен того самого Юзера. Как видите ни Юзер, ни User-Agent и клиентские скрипты не увидили настоящий Токен. Его знают только Клиент и Провайдер — в идеале.

Дальше Токен используют для совершения API запросов, впоследствии его можно рефрешить (для этого вместе с Токеном возвращается refresh_token).

Threat Model или Я Знаю О Чем Вы Подумали


Длинный, но правильный путь безопасно пользоваться OAuth.
Здесь же отмечу:

1. А что если я подставлю такой redirect_uri который будет вести на свой сайт а потом использую этот Код сам для аутенфикации?

Все redirect_uri должны находится на домене Клиента. Часто разрешен еще домен Провайдера. Ваша ссылка вернет redirect_uri_mismatch.

2. ОК а что если я найду такое место на сайте Клиента, которое сольет мне Код? Может открытый редирект вида site.com?url=http://outsite.com или hot-linked картинку которая сольет реферер?

Каждый Код привязан к тому redirect_uri для которого он был выпущен и для получения Токена Клиент передает «правильный» redirect_uri. Если ваш Код был выпущен для clientsite.com/leak_referer а Клиент при получении Токена отошлет clientsite.com/facebook_callback то Провайдер не отдаст Токен.

3. А можно ли как то слить Код передав правильный redirect_uri?

Нет, т.к. любая правильная реализация Клиента обязана сразу редиректить на другую страницу после получения Кода — так что Код не виден даже в истории браузера.
Даже если вам удалось достать код для правильного redirect_uri то т.к. он уже был 1 раз использован он больше не активен.

4. Допустим у меня есть Код для моего аккаунта в соц сети выпущенный для правильного redirect_uri. Что будет если я заставлю посетить эту ссылку Васю?

В таком случае сайт Клиента подумает что ваш аккаунт в соц сети принадлежит Васе. И присоеденит его. Чтобы типичного CSRF не происходило Клиент обязан сохранять рандомное значение state в сессии/куке и проверять по возврату на колбэк на соответствие. Хотя, так почти никто не делает (точнее не делали).

Реальность


FB Reply Attack

Facebook Connect уязвим для классической Replay attack.
Демонстрирует пункт 3 — после одного использования кода он еще может быть использован для аутенфикации в течение 60-80 минут — стандартное expire_in токена. Реплай атака в чистом виде.
Допустим, на сайте нашли XSS — примерно такой инжект поможет вытащить код для правильного redirect_uri через document.referrer фрейма.


Репорт сделан, скоро исправят. Ну как скоро, месяца три, enterprise же
Hijacking Account

Самая частая уязвимость, присутствует например на хабре — об этом ниже. Нарушается пункт 4.
Если вы видите в запросе state не спешите сдаваться. Что хабр, что digg.com не видели разницы между a12b6467c3fb385e237109502277ab26 и heyman0day123123

VK redirect_uri
Реализация сделана по каким-то древним манускриптам — отсутствие проверки для какого redirect_uri был выпущен Код это грубое нарушение пункта 3.
Смотрите, вот ссылка Жмите, не бойтесь, и откройте Network Panel. Видите запрос?

Реферер слился, код слился — теперь его можно использовать чтобы зайти в ваш аккаунт через ваш вконтакте(если вы его привязали) уже про правильному redirect_uri.
Репорт сделан неделю назад, ответов от живых людей и от ботов не поступало.
misc

Если вы используете Implicit Flow — т.е. получаете access_token от юзера напрямую, то нет никаких гарантий что этот токен принадлежит текущему юзеру. Он мог его просто украсть через вредоносного Клиента, и использовать чтобы попасть в аккаунт какого-то юзера на вашем сайте.
Обязательно проверяйте, был ли выпущен данный токен для вашего client_id.

Так же OAuth2 не дает никаких гарантий что пользователь дал вам те разрешения что вы запросили на этапе авторизации в параметре scope. Он мог просто удалить их — в итоге вы должны на колбэке проверить разрешил ли он что вы запросили.

Если на сайте найдена XSS то есть легкий способ украсть кучу access_token-ов. Возьмите authorize URL который сайт использует для facebook и замените response_type=code на token. Осталось вставить этот URL во фрейм, дождаться возврата токена в ссылке вида callback#access_token=123, срезать токен и слить его. Спамьте на здоровье!

0day на хабре?


Тот же эксплоит работает и для facebook/google только сложнее получить redirect_uri с кодом без использования его — нужен NoRedirect+FF
Поэтому демо на VK. дяденька не бань UPD: уязвимость исправили.

1. У вас не должно быть привязанного VK. Авторизуйтесь oauth.vk.com/authorize?response_type=code&client_id=3110645&redirect_uri=http%3A%2F%2Fhabrahabr.ru&scope=offline&display=page
2. Вас вернуло на habrahabr.ru/?code=CODE
3. Заставьте другого хабраюзера посетить habrahabr.ru/social/callback/vkontakte/?code=CODE&state=whogivesafuckaboutstate можно подкинуть саму ссылку, но лучше спрятать в img или iframe и тд. Если он залогинен на хабре ваш ВК привяжен к его хабрааккаунту.
4. Логинимся через ваш ВК в его хабрааккаунт, меняем его аватар на nayncat.


Мораль


Вконтакте: перестаньте игнорировать письма в Поддержку, либо попросите ваших разработчиков снизойти поговорить с простым смертным. А еще введите bounty program (sarcasm: есть риск разориться на ней).
Хабр: рекомендую выключить Ключницу на время и пофиксить уязвимость путем правильной проверки 'state' со значением из cookie/session.

Road to Hell короче. Вы можете присоедениться к разработке принципиально нового стандарта с нескучными токенами — OAuth2.a (Charm — Provider). Печенек, правда, нет.

Egor Homakov (@homakov) & isciurus #RT pls


UPD2:
Хабр закрыл уязвимость, вконтакте добавил проверку redirect_uri для всех новых приложений и нотификацию для старых, facebook «We will only allow authorization codes to be exchanged for access tokens once and will require that they be exchanged for an access token within 10 minutes of their creation » и «After reviewing the bug details you have provided, our security team has determined that you are eligible to receive a bounty payout of $2,000 USD», я опять поднял чсв, люди строят предположения о перспективах bounty vk.
Sakurity.com @Chikey
карма
348,0
рейтинг 0,0
Info Security
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

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

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

  • +44
    разрыв шаблона. «Блогозапись» и не Мицгол >_<
    • +9
      Replay attack
    • +22
      Да и вообще, к чему этот англицизм, «блог», когда есть русский аналог «паутиножурнал»‽
      • +5
        Дневник, нет?
      • +14
        Лично я за «летописи»
      • 0
        Мой вариант — заметки.
      • +1
        Веды?
        • 0
          Блог Кришны в соавторстве с Арджуной или «полевые заметки с места Битвы на Курукшетре»?
    • +2
      Шаблон рвался не долго: «вольный пересказ спек OAuth2»
  • 0
    Все суета сует.
    • +5
      Все кто дочитал до сюда — поздравляю, вы выбрались из лап nyancat-а!
      • +3
        Читаю ваш комментарий и всё ещё вижу nyancat-а!
  • +10
    Спасибо; сохранил ваш пост по Ctrl-S.
  • +2
    спрашивайте ваши ответы кстати, если какая то атака не понятна
    • НЛО прилетело и опубликовало эту надпись здесь
      • +1
        реплэй аттака производится уже Mallory — т.е. хакером. У меня есть код другого фб, я его подставляю в свой колбэк, стэйт тоже свой. Но вхожу уже в ваш аккаунт
        • НЛО прилетело и опубликовало эту надпись здесь
          • +1
            на каком сервере?
            на колбэк переходит браузер пользователя. фильтровать айпи тут клиенту нет смысла
            • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      ВК, для новых приложений теперь возвращаем ошибку, для старых дополнительные поля error и info. Спасибо за информацию.
      • 0
        быстро получилось! ВК опередит фейсбук на 89 дней и пару часов, по моим подсчетам
      • 0
        Спасибо в стакане не булькает. ;)
  • 0
    «После прочтения сохранить» — применимо ко многих постам на Хабре
  • +1
    T.chikey.nyancat.post ;)
    • 0
      вы все еще используете T-for-translate?! вообщето это был бэкдор, посоны
      • 0
        После коммента не могу разлогинится с хабра. Есть подозрение, что T сработал и здесь… :)
  • +1
    Мне мерещится, что хабр уже сам такой большой, что мог бы выступать в роли провайдера.
    • 0
      маловато Resources чтобы давать клиенту. Комменты, посты, личные сообщения и по мелочи. Нет профита отдавать авторизацию кому то
      • +3
        Зато +10 к ЧСВ зашедшему через Хабр)))
        • 0
          а вот как провайдер Аутенфикации сгодится. Аутенфикация нужна только возвращать аватарку, имя и user_id. Любой сайт для этого сгодиться с профайлами
          • +1
            я именно про это
  • +1
    Почитал описание вашего Oauth2.a — как-то все не очень внятно. Вроде читаешь — правильные слова, а смысл всей это канители ускользает. Не проще ли было бы показать отличия от стандартного OAuth2.draft31, пояснить что «вот в Oauth2 с нашей т.з. есть такие проблемы, неудобства и все такое, мы решаем это вот таким способом....»

    Зачем серверу передавать указание на display=popup\page? Вы хотите в стандарте обязать сервер показывать страницы конкретными способами? Почему не использовать разные URL для этой цели, например authorize_page authorize_popup- и серверу проще и в стандарт не просочится далекое от авторизации требование…
    • 0
      в том то и дело что стандарт oauth2.a проще описать своими словами чем перечислить все отличия. вот они:
      1. code access_token refresh_token — все это token
      2. mass refreshing токенов
      3. scope может быть frozen/agile
      4. 1 redirect_uri, scope, response_type на всего клиента

      насчет урлов — роутеру приложения как раз не удобно парсить auth_1 auth_2 и проще принять это как параметр. вообще, display это как пример. в стандарте ему делать нечего
      • 0
        1.Это вроде имеет принципиальное значение для TokenExchange Endpointа
        2. Отписал ниже
        3. Непонятно, что значит frozen\agile, пояснений этим терминам нету, каждый подразумевает что угодно, например я считаю что это «замороженный\гибкий» — откровенная хрень в данном контексте, но ничего другого придумать не могу, а пояснений нету.
        4. Отписал ниже

        Тогда где есть описание предлагаемого именно «стандарта», а не «своими словами»?
      • 0
        Насчет урлов — вопрос спорный, кому-то удобней параметрами кому-то урлами, фиксировать такое в стандарте — нехорошо.
        Как впрочем и про передачу в #fragment части — вы принуждаете пользоваться яваскриптом для парсинга.

        Не всегда на конце User будет находиться браузер с поддержкой яваскрипта и вообще с человеком, смотрящим в дисплей. Там может быть любая другая автоматическая хрень, которая умеет отрабатывать редиректы и авторизовываться на сервере (отправлять постом логин пароль, куки, кликать на кнопку и т.д). Требование яваскрипта или разбора #fragment на клиенте — спорное.
        • 0
          не спорное. как раз эти моменты давно утверждены в OAuth2 и им нет замены. Объяснять долго но суть в том что хеш не идет на сервер что предотвращает лики
          • 0
            в Oauth2 есть сценарий, который позволяет это обойти, Server-side flow, где реально нужны только редиректы а не разбор #fragment на клиенте. У вас такого варианта вроде как нету.
            • 0
              поясните. у меня есть оба сценария. только server/client side выбирается в настройках приложения а не параметром в урл
              • 0
                Тогда зачем обязывать передавать и разбирать в fragment секции если сами указываете, что не все сервера могут ее получить. Или это подразумевалось только для implicit flow? Тогда явно укажите это в readme
            • 0
              • –1
                не читаю на ruby
            • 0
              кстати насчет agile/frozen homakov.blogspot.com/2012/08/how-to-cheat-on-facebook-apps.html

              There are two ways to fix it (OAuth2.a deals with the issue this way):
              1) when app has «frozen» scope. This is not param in URL anymore, just a field in the database. Developer don't need to check what is allowed anymore — he is sure.
              2) when app has «agile» scope. Client 'proposes' scope and User can uncheck not desired permissions. App should check explicitly what is allowed for certain token.
              • 0
                это должно быть в т.н. стандарте, а не в вашем блоге.
              • 0
                Советую тогда еще добавить концепт, что приложение само может иметь ограничение по scopes — очень удобно раздавать так права на API приложениям, например доступ к некоторым частям API закрывается исходя из того, что приложение не имеет права запрашивать определенные scope и они явно выкидываются из запрошенного набора, даже не показываются пользователю.
                • 0
                  dostup zakrivaetsya smotrya na nalichie scope — tak eto samaya osnova oauth2. poyasnite eshe raz?
                  • 0
                    Помимо того что у токена есть набор scopes (заказаных при создании), у самого client application есть свой набор scopes. Когда клиентское приложение обменивает подписанный токен (code) на access_token — в ответе с access_token приходит фактически выданный scope.

                    Ну например — у приложения нет возможности обращаться к API со scope=user_management, пока представители явно не заключат юридический договор с провайдером api.
                    Приложение может попытаться отправить юзера за токеном с таким scope, но при фактической выдаче access_token ( у нас реально еще при показе гуя пользователю т.к. мы сами генерим токены которые потом должны прийти на подпись) из запрошенного перечня scope выкидывается набор scope который отсутствует еще и у приложения.
                    Велосипед конечно, но оказался довольно удобным для разруливания прав приложения
                    • 0
                      Помимо того что у токена есть набор scopes (заказаных при создании), у самого client application есть свой набор scopes. Когда клиентское приложение обменивает подписанный токен (code) на access_token — в ответе с access_token приходит фактически выданный scope.

                      да. это вопрос или ответ? все правильно

                      вашу идею понял. привнести еще один лэер скоупов — более крутой с доп правами, только для избранных. хорошая идея
                      • 0
                        это не вопрос или ответ, это пояснение которое вы попросили ;)

                        Просто помимо вычитания привилегий из agile scope, которые выкинул пользователь, вы сами выкидываете те scope которые не разрешены самому client app
                        • 0
                          итого пользователь увидит пересечение ТО_ЧТО_РАЗРЕШЕНО_ДАННОМУ_КЛИЕНТ
                          ТО_ЧТО_В_ПАРАМЕТРЕ_SCOPE
                          • 0
                            да
    • +1
      ээх нету редактирования коммента…
      И да, в вашем упрощении вы потеряли одну сторону — у вас провайдер ресурсов и сервер авторизации — один юнит.
      Это далеко не всегда так. Возьмите тот же гугл — сервер авторизации и resource provider — очень разные сущности. Это функционально нужно и полезно, зачем сводить это до единственного provider у которого и API и Oauth2 интерфейс для авторизации клиентов, заранее сужая область применения.
      Да и mass refresh — имхо сомнительная фича. Вы хотите чтобы клиент мог одним запросом к серверу обновить over9000 токенов? Ну я так заддосю сервер — отправляя все токены каждую секунду на рефреш я буду порождать избыточное потребление ресурсов — серверу все токены надо перевыписать, а если для выписывания используется не рэндом а крипта какая-нить — будет еще печальней.
      Не лучше ли делать lazy-refresh — т.е. реально когда понадобится обратиться к апи а токен протух — сделать быстрое обновление 1 токена…

      Последнее требование в readme.md совсем стремное:
      Every Client has 1 redirect_uri, 1 scope and 1 response_type. They are defined in settings of application and cannot be changed during authorization process(scope can be adjusted if it's not «fixed»).

      Выходит, я при всем желании не могу авторизовать через такого провайдера несколько своих сайтов, если они предоставляют разный функционал на разных URL. И такая полезная штука как Single SignOn ( залогинился в одном сайте компании = залогинен везде, например StackOverflow Network) должна быть реализована вручную, а не через implicit авторизацию. Плохая идея.
      • 0
        >И да, в вашем упрощении вы потеряли одну сторону — у вас провайдер ресурсов и сервер авторизации — один юнит.

        это пока. потом выпилю авторизацию в gem railsengine и это будут отдельные вещи

        >Да и mass refresh — имхо сомнительная фича. Вы хотите чтобы клиент мог одним запросом к серверу обновить over9000 токенов? Ну я так заддосю сервер — отправляя все токены каждую секунду на рефреш я буду порождать избыточное потребление ресурсов — серверу все токены надо перевыписать, а если для выписывания используется не рэндом а крипта какая-нить — будет еще печальней.
        Не лучше ли делать lazy-refresh — т.е. реально когда понадобится обратиться к апи а токен протух — сделать быстрое обновление 1 токена…

        именно, чтобы одним запросом обновил кучу токенов. какой дос Оо. все это легко пресекается. Мас рефреш не обязателен, можно отсылать и по одному токену, только это неудобно не для провайдера не для клиента. Куда лучше по крону раз в час обновлять нужные вещи. подумайте еще раз, фича отличная

        >Выходит, я при всем желании не могу авторизовать через такого провайдера несколько своих сайтов, если они предоставляют разный функционал на разных URL. И такая полезная штука как Single SignOn ( залогинился в одном сайте компании = залогинен везде, например StackOverflow Network) должна быть реализована вручную, а не через implicit авторизацию. Плохая идея.
        зашибись идея. читайте threat model внимательно. Это все как для секюрити так и для удобства. Если вам принадлежит 1 redirect_uri то вы с помощью ловкости рук и никакого мошенничества запишите код в сессию и перекинете пользователя на подсайт. Implicit вообще должен использоваться когда нет бекенда. Например мобильные приложения или чисто js сайт.

        никаких неудобств это не создает, зато предотвращает массу атак которые я изучал
        • 0
          mass refresh — хорошо для школьных приложений с 10 пользователями. Проверните его на сайте с 100к или с 1млн пользователей — захлебнетесь, по крону, раз в час. А если реальные обращения пользователей происходят раз в день (99% браузерных игр) — 23 бессмысленных масс обновления.

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

          Ну и последний гвоздик — представьте у вас 100к пользователей, обновление раз в день (даже не в час) по крону, но реально активных осталось 10. Ради этих 10 вы будете гонять несколько мегабайт запросов (100к строк в json даже если убрать ненужный token — при длине токена в 64 символа — дает 6,4Мб на запрос). Ну или будете придумывать всякую хрено-эвристику — этих обновляем, этих не обновляем, потому что столько раз не заходили.

          Поверьте, индукодеры не будут этим заниматься и будут слать все токены всех пользователей каждый час. Потому что «стандарт позволяет» создавать такую нагрузку. А сервер, как дебил, имплементировавший такой стандарт, будет с этим бороться, тратя свои ресурсы.

          Про multiple URL — ну да, при таком отношении к критике — пусть будет зашибись.
          В чем проблема на сервере проверять redirect_uri не на совпадение с 1 строкой, а на совпадение с несколькими. Я в настройки своего приложения впишу, естественно только те URL которыми владею и обслуживаю. И речь не про разные страницы на 1 сайте типа site.com/authorize_url1 и site.com/authorize_url2 ( которые могут быть leaky), а именно про разные сайты — site1.com/authorize_url и another-service-site.com/authorize_uri
          Stackoverflow как пример был приведен не просто так, там не поддомены, а абсолютно независимые домены. Хотя не удивлюсь если они там сами реализовали Single SignOn — там рукастые девелоперы.
          • –1
            1 ponyal vash point. da tut nado podumat chtoby mass refresh ne poshel vo zlo

            2 kstati v OAuth2.a or Lets just fix it poste ya i predlagal ispolzyovat neskolko redirect_uri s exact_match. vposledstvii ya reshil chto 1 redirect_uri dlya prilozhenya OK. est' workarounds dlya site.com site.net obhodov. vprochim ya tozhe podumayu ob etoy funkcii — ne ochevidno dlya developera

            stackoverflow krutoy site v plane oauth-brokera. smotrite, v chem problema
            site.com/redirect1?code=123 redirects to anothersite/redirect1?code=123 esli oni oba imejut client_credentials? problem nikakih dlya poluchenia tokena uzhe s drugogo domena. workaround est' a znachet OK

            sorry za mnoy gonitsa mi6, pishu translitov
            • 0
              1. А ну да, еще насчет json — там надо быть осторожнее т.к. например дефолтный форматтер в json на .NET не разбирает больше 2Мб текста. Datacontract — вообще 8кб на объект. Понятно что «проблемы негров», но есть реальный мир с реальными библиотеками и у них есть вполне внятные ограничения. ;) это JFYI

              2. Ну да, девелоперы в одном экшене должны не только разрулить display=popup|page но и определить что надо сделать редирект. Зачем? ну посмотрите у реализаций от гугла или фб есть прекрасное и простое решение — несколько доверенных адресов. И в итоге redirect_uri проверяется на соответствие нескольким маскам. Кстати строгая фиксация uri тоже лишнее ограничение. State можно как в виде параметра передавать так и в виде части страницы, например mysite.com/Authorize/123456 или mysite.com/Authorize?state=123456. Оба варианта имеют право на жизнь (а первый еще и по логам искать проще ;) )

              Я не понимаю немного всей логики — раз есть workaround — значит пусть остается косяк а все должны пользоваться этим workaround? Стандарт или даже протокол который навязывает использование workaround'ов-костылей — не имеет смысла.

              Про mi6 зачетная отмаза ;)

              • 0
                1. наслышан наслышан. однажды коллега писал парсер json под .net кажется. такого рода проблем и проблем с кодировками было масса.
                я и не говорю что JSON надо делать везде обязательным. hyper media apis как говорит т-щ @steveklabnik

                2. тут довольно принципиально
                не по маске а четкое соответствие чтобы лики были невозможны и как следствие не требовалось бы требовать redirect_uri и вообще запоминать его в базе данных. Огромное упрощение как БД так и самого flow.
                1 или несколько exact match redirect_uri — это уже не сильно большая разница. На данный момент я решил что 1ого как минимум достаточно. Это, как минимум, позволяет не создавать целую таблицу редирект ури и еще куча доп мишуры. А девелопер пусть со своим единственным redirect_uri делает что хочет — прячет в сессию, редиректит итд.
  • 0
    XSS (англ. Сross Site Sсriрting — «межсайтовый скриптинг» см. также Homakov) — тип атаки на уязвимые...
  • +1
    обновил пост
    • +1
      *блогозапись. простите
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      чувак ты гений кароче. пошел писат ьспамилку
  • 0
    asd

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