Хакер использовал уязвимость в Android-приложении, чтобы бесплатно выпить пива

Краткая версия событий

Польский разработчик Куба Грецки обратил внимание на Android-приложение, функционал которого заключался в следующем: после оплаты в пабах, кафе и ресторанах покупатель может получить бонусные баллы, приложив к смартфону специальный маячок или введя PIN-код для подтверждения транзакции. 

Разумеется, Куба стал искать способ подделки сигнала от этого маячка. Сперва он нашёл производителя этих маячков, им оказалась компания Estimote. Ему повезло, ведь компания выпустила SDK с подробной документацией.

Получив всю техническую информацию (а производители даже выложили в открытый доступ Android-библиотеку для работы со своими маячками), он использовал программу Fiddler для слежки за HTTP/HTTPS-пакетами и их подделки.

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

Он настроил дешёвый VPS-сервер и создал VPN-подключение для смартфона. Целью всего этого был перехват пакетов данных от маячка. Разобравшись с программной частью, он отправился в рестораны, участвующие в этой программе. Совершив покупку и получив за неё баллы, хакер перехватил нужный пакет и фактически получил возможность отправлять на сервер фальшивые пакеты с верными данными, «зарабатывая» таким образом баллы.

Полная версия с техническими деталями

Рассказывает Kuba Gretzky


Совсем недавно я наткнулся на Android-приложение, которое позволяет вам получать бесплатные закуски и напитки в различных пабах, ресторанах и кафе в обмен на баллы, выданные за прошлые покупки. Во время покупки вы говорите продавцу, что хотите получить баллы и выбираете нужный пункт в приложении. Для верификации продавец должен либо провести по устройству специальным маячком, либо ввести PIN-код.

Например, в одном из мест бесплатное пиво можно получить за 5 баллов, а за каждый купленный бокал вы получаете 1 балл, т.е. каждый шестой бокал становится бесплатным.

Все любят халявное пиво, поэтому первое, о чем я подумал — насколько защищён процесс верификации и как именно работают эти магические маячки?

И что более важно, можно ли как-то обойти защиту приложения и таки попробовать халявное пиво?

Magical Beacon

Я специально не упоминаю имя приложения, поскольку оно доступно только для моей страны (Польши). Я лишь хочу показать пример типичной уязвимости такого приложения, как их искать и как лучше защитить приложение. В этом рассказе я буду использовать вымышленное название для этого приложения — «EatApp».

Проводим исследование

Сперва я решил разобраться в тех маячках, что используются с приложением. Очевидно, что они связываются с мобильным устройством через Bluetooth.

Совсем скоро я нашёл компанию, производящую эти маячки — Estimote, и вот что они пишут про свою технологию:

Маячки и стикеры Estimote являются маленькими беспроводными сенсорами, которые можно прикрепить к любой поверхности или предмету. Они передают радиосигналы, которые распознаются вашим смартфоном.

Используя Estimote SDK, приложения способны понимать, что находятся рядом с нужным объектом, определяя их тип, владельца, расположение, температуру и скорость движения.

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

Estimote Tech Overview 1

К счастью, Estimote выпустила SDK с очень подробной документацией.

Это позволило мне узнать больше технических подробностей:

Estimote Beacon — это маленький компьютер. Его 32-битный процессор ARM® Cortex M0 использует акселерометр, датчик температуры и 2.4 ГГц радиопередатчик Bluetooth 4.0 Smart, также известный как BLE или Bluetooth low energy.

Также я узнал, что маячки транслируют следующие значения:

  • UUID — чаще всего представлен строкой, например “B9407F30-F5F8-466E-AFF9-25556B57FE6D”;
  • Minor и Major — два целых числа, принимающих значения от 1 до 65535 (0 — зарезервированное значение).

Отлично! Также они предоставляют свою Android-библиотеку для упрощения разработки приложений. Вот один из примеров настройки слушателя маячка:

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

Когда приложение ожидает, чтобы продавец провёл маячком по устройству, оно слушает пакеты с конкретным UUID. Если пойман нужный пакет, оно определяет расстояние от маячка до устройства. Если расстояние невелико, приложение использует те два числа в качестве ключей валидации и отправляет их на сервер EatApp.

Мне стало интересно, на какое расстояние маячок может передавать сигнал. Искать долго не пришлось:

Маячки Estimote могут передавать сигнал на расстояние до 70 метров (230 футов). Однако, в реальных условиях стоит рассчитывать лишь на 40–50 метров.

Вау, целых 70 метров? Похоже, что, теоретически, ключи безопасности (UUID и два числа), которые скорее всего используются для авторизации наград, транслируются по воздуху. Это нехорошо.

Узнав это, я стал искать способ перехвата и чтения этих пакетов. Разработка своего приложения заняла бы у меня несколько дней, но Estimote предоставляет свое приложение для отладки, Estimote Developer App. Оно предоставляет всю необходимую информацию.

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

Работаем с Fiddler

Для перехвата мобильного трафика я использовал компьютер под Windows. Лучшим бесплатным HTTP/HTTPS-прокси, который я знаю, является Fiddler.

Настройка

Для активации перехвата HTTPS откройте Tools > Telerik Fiddler Options > HTTPS и убедитесь, что у Capture HTTPS CONNECTs и Decrypt HTTPS traffic стоят галочки. Также убедитесь, что в Tools > Telerik Fiddler Options > Connections вы включили опцию Allow remote computers to connect.

Также вам потребуется экспортировать сертификат Certificate Authority. В той же вкладке нажмите Actions и Export Root Certificate to Desktop:

Export CA from Fiddler

Это поместит файл FiddlerRoot.cer на рабочий стол. Fiddler будет использовать этот файл для подделки сертификатов каждого HTTPS-соединения, проходящего через прокси. Конечно же, такие сертификаты не будут доверенными для вашего смартфона, поэтому вам нужно перенести сертификат Fiddler на ваше устройство и пометить его как надёжный.

Для этого скопируйте FiddlerRoot.cer на карту памяти вашего смартфона. Откройте Настройки > Безопасность и выберите Установить с карты памяти:

Install from SD card

Найдите и выберите нужный сертификат. Теперь прокси будет помечен как надёжный, и вы сможете работать с HTTPS-трафиком. Убедитесь, что телефон и компьютер работают в одной сети.

Перед тем, как продолжить, выясните, какой порт слушает Fiddler, открыв Tools > Telerik Fiddler Options > Connections:

Fiddler's proxy port

После этого выясните локальный IP-адрес вашего компьютера. Откройте командную строку и введите ipconfig.

На вашем Android-смартфоне откройте Настройки > Wi-Fi и найдите активную беспроводную сеть. Нажмите на неё и удерживайте в течение 2 секунд, после чего выберите в выпадающем меню Изменить сеть. Нажмите на Дополнительные параметры и долистайте до настроек прокси. Выставьте ручной тип, а в качестве хоста и порта прокси укажите IP-адрес компьютера и прокси-порт Fiddler’а. В моём случае это были значения 192.168.0.14 и 9090.

Если вы всё сделали правильно, то теперь можете видеть мобильный трафик в Fiddler.

Ловим трафик

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

Я открыл EatApp и запустил диалог Earn Points для случайно выбранного ресторана. Поскольку у меня не было маячка этого ресторана, для верификации мне пришлось использовать вариант с PIN-кодом. Я ввёл рандомный ПИН и проверил пакеты, которые перехватил Fiddler.

Отправленный запрос:

Полученный ответ:

Запрос прост. Параметры отправляются в виде JSON-данных, которые не хэшируются, PIN также не шифруется. Названия параметров говорят сами за себя

  • authentication_token — аутентификационный токен, полученный от сервера в процессе входа. Это значение уникально для каждого аккаунта EatApp.
  • promoted_products_ids — массив ID продуктов, за которые можно получить баллы.
  • pin — ну, тут всё понятно.
  • place_id — уникальный ID заведения.
  • isDoneByGesture — вот про этот ничего сказать не могу, но полагаю, что он устанавливается в true, только когда вы тратите баллы.
  • longitute и latitude — последние известные GPS-координаты, позволяющие определить местонахождение пользователя. Могут использоваться в качестве средства безопасности, если пользователь находится далеко от ресторана.

Я попробовал сбрутить PIN. В конце концов, комбинаций всего 10000. К сожалению, после отправки 5 запросов с различными ПИН-кодами, сервер начал отправлять следующий ответ:

Если бы у меня были несколько сотен аккаунтов, между которыми я мог бы переключаться во время брутфорсинга, то 30 минут ожидания не были бы большой проблемой.

Глядя на параметры запроса верификации, я подумал, какими бы они были при верификации маячком.

Одной идеей по получению бесплатных очков был перехват пакета с верным ПИНом, введённым сотрудником ресторана. А если бы использовался маячок, я смог бы получить точные параметры запроса.

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

Что ж, я решил настроить VPN-перехватчик, чтобы использовать его с 3G/4G-подключением моего смартфона. VPN-сервер перехватывает и расшифровывает HTTPS-трафик таким же образом, как и Fiddler.

Злой VPN

Сперва мне нужен был VPS-сервер, на который я бы мог установить VPN-софт. Самым быстрым и надёжным вариантом на Linux стал Digital Ocean. Я создал самый дешёвый: Debian 8 1CPU 512MB RAM droplet за 5 долларов в месяц, или 0.7 цента в час. Этого было более чем достаточно.

Я должен был решить, какой VPN-протокол использовать. Android официально поддерживает PPTP и L2TP. Я знал, что PPTP считается ненадёжным, и для него недоступна функция Always-On, которая обеспечивает повторное подключение телефона к VPN при потере соединения, а также обеспечивает передачу всех пакетов только через VPN, что крайне важно в нашей ситуации.

Найти хорошее руководство по установке L2TP VPN на Debian 8 было очень сложно, поскольку большинство использовало Debian 7, а в следующей версии некоторые зависимости были изменены.

Наконец я нашёл отличный способ установить IPsec/L2TP VPN при помощи автоматических скриптов. Вот всё, что мне пришлось сделать на сервере:

YOUR_IPSEC_PSK — заранее созданная ключевая фраза (например, mysecretpskforvpn).
YOUR_USERNAME и YOUR_PASSWORD — логин и пароль для входа в VPN.

Сделал и забыл! VPN запущен и работает. Теперь нужно запустить VPN-подключение на смартфоне. Я зашёл в Настройки > Беспроводные сети > VPN и добавил новую VPN со следующими настройками:

Имя: vpn_interceptor
Тип: L2TP/IPSec PSK
Адрес сервера: [VPN_SERVER_IP] Секретный ключ L2TP: (не используется)
Идентификатор IPSec: (не используется)
Ключ IPSec: [YOUR_IPSEC_PSK] Поисковые домены DNS: (не используется)
Сервер DNS: 8.8.8.8
Маршруты форвардинга: (не используется)

После этого я нажал кнопку настроек в том же окне и выбрал опцию Always-On, а затем подключился к новому VPN-соединению.

И вот тут я столкнулся с проблемой, связанной с Android 6.0. Чтобы я не делал, всё время возникала ошибка подключения. Я попробовал другой девайс на Android 4.4, и с ним всё работало отлично. Действительно, страничка скрипта на GitHub упоминала Android 6 Marshmellow:

Замечание: пользователи Android 6 (Marshmallow) должны изменить /etc/ipsec.conf на VPN-сервере и дописать ,aes256-sha2_256 к строкам ike= и phase2alg=. Затем нужно добавить строку sha2-truncbug=yes, сразу после предыдущих. Разделите строки двумя пробелами и запустите service ipsec restart.

Это не сработало, и я откатил все изменения. Потом, в настройках VPN, я нашёл опцию Режим обратной совместимости, включил её и всё заработало.

После этого нужно было настроить перехват и дешифровку пакетов HTTPS. Для этого я решил использовать SSLsplit. Установка была лёгкой:

Скрипт IPSec/L2TP создал несколько настроек фаервола, которые не позволяли мне перенаправлять пакеты в прокси sslsplit. Я решил полностью удалить настройки iptables и заменить этот файл пустым, добавив в него нужные параметры.

Пересылка пакетов уже включена из-за скрипта, но на всякий пожарный вы можете запустить следующее:

После этого я создал директории для логов sslsplit и создал рут-сертификат для подделки HTTPS-сертификатов, аналогично тому, что делал в Fiddler:

Мне пришлось скачать с VPS сертификат sslsplit/certs/ca.crt, скопировать его на карту памяти и пометить как надёжный. Этот файл можно легко скачать на Windows через протокол SSH, используя WinSCP.

Для простого включения/выключения перехватчика я создал два маленьких скрипта (моя директория sslsplit была создана в /root/):

/root/sslsplit/start.sh

/root/sslsplit/stop.sh

Теперь для запуска мне достаточно было запустить файл ./start.sh, а для остановки — ./stop.sh. sslsplit был настроен для работы в качестве «демона» и сохранял все пакеты в отдельных файлах в /root/sslsplit/logs/. Что грустно, sslsplit не полностью декодировал сохранённые пакеты, но об этом позже.

Теперь, когда всё было настроено, я наконец смог поехать в город, поесть и поймать парочку пакетов!

Поездка в город

Я посетил три места, заказав в каждом немного еды. Каждый раз я просил продавца начислить мне баллы. Мой телефон был постоянно подключён к VPN, а пакеты авторизации складировались на сервере при помощи sslsplit.

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

Наконец я смог воспользоваться официальным приложением Estimote для разработчиков, которое находило близлежащие маячки и получало их значения UUID, Major и Minor. Оказалось, что моя теория была верна:

Estimote Screenshots

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

Расставляем точки над i

Я скачал логи с сервера. Проблемой sslsplit было то, что он сохранял все пакеты в сыром виде, то есть, если Transfer-Encoding имеет значение chunked или пакеты сжаты gzip, скрипт их не расшифрует.

Я написал маленький скрипт, дешифрующий пакеты. Обратите внимание, что он использует забагованную библиотеку http-parser. Сам скрипт можно найти на Github.

Пример запуска:

Я быстро нашёл пакет, который был отправлен во время контакта смартфона с маячком:

Джекпот! Я убедился, что найденные в пакете UUID, Major и Minor полностью совпали с числами, которые я видел в приложении Estimote. Это означало, что я был прав, и ключи верификации постоянно транслируются в каждом ресторане, участвующем в программе.

Вот пошаговый гайд по получению бесплатных баллов EatApp в ресторане ZZZ:

  • Зайдите в ресторан ZZZ.
  • Откройте приложение Estimote для разработчиков и найдите ближайший маячок.
  • Сделайте скриншот с увиденными значениями UUID, Major и Minor.
  • Идите домой.
  • Установите брейкпоинт в Fiddler для перехвата пакетов EatApp с путём /users/ в запросах GET.
  • На телефоне выберите ресторан ZZZ restaurant и включите ожидание PIN-авторизации для начисления баллов.
  • Введите любой PIN.
  • Измените перехваченный пакет, убрав запись "pin":"NNNN" и заменив её на "main_beacon":{...} с ключами из приложения Estimote..
  • Отправьте изменённый пакет на сервер EatApp.
  • Наслаждайтесь халявой!

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

Заключение

Публичная трансляция ключей авторизации — плохая идея. Вот несколько способов улучшения безопасности EatApp:

  • Отправляйте хэшированное значение текущего состояния аккаунта в качестве дополнительного параметра запроса (количество баллов, полученных в каждом ресторане, имя аккаунта, последнее местоположение и т.д.). Сервер авторизует запрос только при корректности хэша. Выяснить свойства хэша можно будет только после реверс-инжиниринга приложения. Вряд ли кто-то станет заморачиваться.
  • Для усложнения реверс-инжиниринга код можно обфусцировать.
  • В маяках также можно активировать функцию Secure UUID, которая шифрует транслируемые ключи. Таким образом, для взлома понадобится корректный ключ шифрования. Для его получения опять же придётся «разобрать» приложение, а потом написать отдельное приложение для дешифровки.
  • Обязательно должна присутствовать проверка сертификатов, что затруднит их подделку.
  • Нельзя доверять устройству клиента! Для максимальной безопасности устройство клиента должно отправлять лишь сам запрос. После этого сервер запрашивает подтверждение у продавца, отправляя запрос на специальное устройство. Таким образом, злоумышленник никак не сможет перехватить запрос для дальнейшей подделки пакетов.

Источник: breakdev.org