Вы со мной не знакомы, но существует известная вероятность, что я знаком с вами. Причина в том, что у меня есть полный, неограниченный доступ к приватной информации миллионов людей, размещённой на аккаунтах Google. Отправленные по почте выписки по банковским счетам, медицинские документы, хранящиеся на Google Drive, сохранённые и пересланные чаты из Facebook, голосовые сообщения на Google Voice, личные фотографии на Google Photos. Список можно продолжать. Никто из них не знает об этом сейчас и никогда не узнает в будущем. Возможно, в их число входите и вы.

И как же я такое провернул? Всё началось с разработанного мной приложения. По очевидным причинам, обнародовать название я не стану. Приложение довольно нехитрое, оно рассчитано на людей, увлекающихся фитнесом, и предлагает возможности типа внесения данных о скорости во время пробежки или готовых комплексов силовых упражнений. Как и многие другие продукты, оно требует, чтобы пользователь первым делом создал аккаунт. По данным аналитики, примерно 60% людей вместо того, чтобы полностью проходить процедуру регистрации, соблазняются заманчивой кнопкой «Войти с Google».


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


У данного пользователя подключена двухфакторная идентификация, поэтому после того, как он ввёл почту и пароль, выскакивает диалоговое окошко, уточняющее, точно ли это он. Местоположение и тип устройства совпадают, поэтому он кликает на «Да».


Вот, собственно, и всё. Теперь человек может спокойно пользоваться приложением, а я, между тем, получаю полный, неограниченный доступ к его аккаунту с удалённого сервера. Ему никогда не придёт никаких сообщений по этому поводу. А если он окажется из дотошных и начнёт изучать сетевой трафик, то увидит, что устройство направляло сетевые запросы только и исключительно на различные поддомены google.com.

Но как такое вообще возможно? Давайте вернёмся к нашей кнопке «Войти с Google». Сразу проясним одну вещь: для тех, кто не в курсе, после нажатия этой кнопки приложение может сделать всё что угодно. Запустить процесс авторизации в Google, издать трубный глас, показать гифку с котиком. Не все варианты из этого списка равно вероятны, но помечтать-то можно.

В моём случае, по клику на кнопку приложение при помощи WebView открывает диалоговое окно и задаёт веб-адрес: accounts.google.com/EmbeddedSetup. Он действительно соответствует странице входа в аккаунт Google, только особой, рассчитанной на новые устройства Android. Это обстоятельство сыграет свою роль позже, когда нам любезно предоставят всю необходимую информацию в виде cookie.

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


Обратите внимание на странную синюю полоску, слова Learn more и примерно всё на правой картинке

И вот теперь-то начинается веселье. Я использую стандартные API, встроенные как в iOS, так и в Android, чтобы внедрить тщательно прописанный фрагмент кода на Javascript, который произведёт необходимые модификации, чтобы страница не отличалась от стандартной ни своим видом, ни поведением.

Догадливые сейчас подумают: «Стоп, так раз можно внедрить код на JavaScript, что мешает просто похитить логин и пароль прямо из текстовых полей?». Абсолютно ничего – вообще говоря, для этой цели уже и готовый код существует. Но в наше время доступа к логину и паролю уже недостаточно. Разве что очень повезёт и сервер окажется в радиусе нескольких сотен миль от местоположения пользователя. В противном случае пользователь получит письмо и оповещение с сообщением о «подозрительной активности» и попытка взлома будет пресечена. А двухфакторная авторизация усложняет нам жизнь ещё сильнее.

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

Когда на устройстве Android впервые проводится процедура авторизации, он отсылает токен, полученный от вышеупомянутой встроенной страницы входа в аккаунт, на особый endpoint. Вот пример типичного запроса:

POST /auth HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 349
Host: android.clients.google.com
Connection: Keep-Alive
User-Agent: GoogleLoginService/1.3 (a10 JZO54K);gzip
app: com.google.android.gmsapp=com.google.android.gms&client_sig=38918a453d07199354f8b19af05ec6562ced5788&callerPkg=com.google.android.gms&callerSig=38918a453d07199354f8b19af05ec6562ced5788&service=ac2dm&Token=oauth2_4%2F4AY0e-l5vPImYAf8XsnlrdshQQeNir3rSBx5uJ2oO9Tfl17LpsaBpGf1E2euc18UyOc8MnM&ACCESS_TOKEN=1&add_account=1&get_accountid=1&google_play_services_version=204713000

Токен в этом запросе берётся из cookies страницы входа в аккаунт, а всё остальное – информация, которая находится в открытом доступе (спасибо, microG!). Та же страница входа в аккаунт улаживает дела с двухфакторной авторизацией – нам вообще не приходится ничего предпринимать.

После этого вышеупомянутый endpoint отсылает тот самый мастер-токен. Но как бы мне получить к нему доступ без подозрительных сетевых запросов? Очень просто: через лог в Google Firebase.

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

И главное: он открывает мне путь ко всем без исключения сервисам, которые когда-либо были доступны с мобильного устройства, от лица владельца соответствующего аккаунта. Достаточно одного запроса POST, чтобы я мог прикинуться официальным аккаунтом Google и обзавестись OAuth-токеном для доступа к чему угодно, включая частные (и, скорее всего, нигде не опубликованные) API. Я могу читать письма, бродить по Google Drive, просматривать бэкапы с телефона и фотографии на Google Photos, а заодно ознакомиться с веб-историей пользователя и поболтать с его друзьями по Google Messenger. Я даже создал модифицированную версию microG, с которой могу управлять всеми этими пользовательскими аккаунтами непосредственно из обычных приложений Google.

И напоминаю, весь процесс выглядит вот так. Предлагаю всем задаться вопросом: а вы бы попались?

Разоблачение


Как многие из вас уже догадались, не всё в этой статье правда. Я не публиковал никаких фитнес-приложений на Play Store и не собирал миллионы мастер-токенов. Спасибо этому материалу за вдохновение. Но сам метод работает. Я, да и любой другой разработчик, определённо мог бы сделать приложение с таким сюрпризом (возможно, кто-то уже и сделал).

FAQ


Но ведь страница отличается от нормального входа в аккаунт. Я бы заметил!

Отличия не так уже бросаются в глаза, так что, скорее всего, не заметили бы. Страница входа в аккаунт Google на Android, как правило, имеет интерфейс типа «выберите аккаунт», но бывают и исключения – например, многие веб-приложения, вроде тех, которые делают на Ionic и Cordova. Большинство iOS-приложений тоже часто отдают предпочтение веб-версии, очень напоминающей приведённый вариант. Кроме того, даже если вам кажется, что отсутствие экрана с «такое-то приложение просит доступ…», вас точно насторожит, то его вполне можно внедрить ценой нескольких лишних часов работы.

Это и на iOS работает?

Я не пробовал, но нет оснований считать, что не сработает.

И что с этим делать?

Вообще, вопрос сложный. Ни одно из моих действий, строго говоря, не подпадает под определение эксплойта, но результат, тем не менее, несёт большую опасность. Для начала Google неплохо бы разобраться со своими уведомлениями насчёт «входа с нового устройства», чтобы они нормально работали. Лично я их получаю, когда пытаюсь зайти в аккаунт с компьютера, но, пока тестировал это приложение, система не сработала ни разу. Другая хорошая идея – обновить гайдлайны, в том, что касается кнопок «Войти с Google»; сейчас там вообще ничего не говорится о требованиях к реализации. Возможно, им стоило бы углубиться в дебри безопасности через неясность – этот принцип, несмотря на все свои недостатки, пока что отлично служит Apple для обеспечения безопасности в iMessage.

Вынужден признать: у меня нет уверенности, что тут можно найти техническое решение, которое полностью устранило бы проблему. Если у официального приложения Google есть возможность выполнить какое-то действие, значит, и сторонние программы при должном старании смогут его повторить. Впрочем, в компании работают неглупые люди, так что поживём – увидим.

Эта проблема актуальна для всех систем авторизации в сторонних приложениях?

Вполне вероятно. Я не разбирался досконально, в каких случаях рассылаются оповещения, а в каких – нет, но даже когда оповещения приходят, из них не всегда понятно, что происходит. Функция «Войти с Apple», как бы там ни было, снабжена очень жёстким гайдлайном, причём администрация App Store (где функция, полагаю, в основном и используется) строго отслеживает выполнение требований. С другой стороны, у них свои проблемы с авторизацией, на фоне которых эта меркнет.

Реальная история


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

Реальная история моего прозрения началась с того, что я разработал приложение-проигрыватель Carbon Player; сейчас оно уже кануло в лету, так и не получив широкого распространения. Приложение замышлялось как замена Google Play Music (помните времена, когда такое существовало?), только с дизайном в разы круче. Чтобы получить доступ к пользовательской папке с музыкой, я перевёл gmusicapi Саймона Вебера на Java, но, переписывая код, поначалу особо не вникал, как там устроен процесс авторизации. Понял только, что нужны логин и пароль пользователя, которые я запрашивал через незамысловатое диалоговое окошко, а потом идут какие-то запросы и вываливаются какие-то токены, которые мне подходят для извлечения музыки.


Перед тем как передать первую версию приложения небольшой группе тестировщиков, я прочесал код, везде добавил логирование и ещё внедрил интерцептор, который должен был автоматически загружать все логи на Firebase. Конечно, не логать пароли мне ума хватило, но три токена, полученные моей имплементацией gmusicapi, я по ошибке залогал. Два из них были довольно безобидными – давали доступ только к разным хранилищам музыки. А вот третий оказался мастер-токеном.

В общем, приложение за всё время своего существования собрало от силы двадцать пять скачиваний, и я быстро махнул на него рукой, чтобы не отвлекаться от учёбы. Но перед этим успел выпустить пару обновлений, в одном из которых появился редизайн новой отпадной (ну, по тем временам) домашней страницы Google Play Music – одного из немногих элементов исходного продукта, которые неплохо смотрелись.


Процесс оказался намного заморочнее, чем я думал, и пришлось неожиданно много заниматься обратной разработкой Protocol Buffers. Что важнее, по какой-то причине теперь там требовался совершенно иной токен, который в gmusicapi реализован уже не был. В итоге, чтобы его внедрить, я на несколько часов зарылся в систему авторизации, пытаясь разобраться, как она устроена. Это привело к ужасному моменту прозрения, когда я осознал, что логировал самую секретную информацию, какую только можно. Скажу одно: логирование прекратилось. Двадцать пять человек, которые скачали приложение, простите меня, пожалуйста (ваши токены я с Firebase удалил!).

Был ещё один, не связанный с первым случай, когда я работал в стартапе, создававшем менеджер паролей. Одним из ключевых преимуществ приложения было то, что оно хранило пароли строго на телефоне, но при этом позволяло авторизоваться с компьютера благодаря букмарклету на JavaScript, который «соединял девайсы» через QR-код. Чтобы всё проходило гладко, когда пользователь открывал сайт на компьютере, приложение обращалось к тому же сайту с телефона и внедряло тщательно прописанный фрагмент кода на JavaScript, который фиксировал логины, пароли и всё прочее. Знакомо звучит?

В конце концов, эти две идеи срослись у меня в голове. У меня был создан прототип Carbon Player, но не хватало времени взять его в работу. Спустя несколько лет я наконец начал создавать на его базе что-то вроде демо-версии. В процессе пришлось многое изменить – метод, описанный в этой статье, значительно отличается от того, что было реализовано в прототипе, поскольку Google внёс изменения в систему авторизации. Но конечный итог остаётся прежним и пугает не меньше, чем тогда.

Если хотите, можете скачать демо-версию и посмотреть на систему в действии; даю слово, что на облако ничего не логируется. Имейте в виду, что приложение очень простое и практически не тестировалось, так что есть немалая вероятность, что метод не сработает, если у вашего аккаунта иная конфигурация. Спасибо, что прочитали статью, надеюсь, вы получили удовольствие от небольшого напоминания о том, насколько важно ставить всё под сомнение. Даже самые безобидные вещи иногда таят внутри что-то не слишком приятное (хотя в случае с тортом-мороженым бывает и наоборот).