Хакер использовал уязвимость в Android-приложении, чтобы бесплатно выпить пива
23К открытий23К показов
Краткая версия событий
Польский разработчик Куба Грецки обратил внимание на Android-приложение, функционал которого заключался в следующем: после оплаты в пабах, кафе и ресторанах покупатель может получить бонусные баллы, приложив к смартфону специальный маячок или введя PIN-код для подтверждения транзакции.
Разумеется, Куба стал искать способ подделки сигнала от этого маячка. Сперва он нашёл производителя этих маячков, им оказалась компания Estimote. Ему повезло, ведь компания выпустила SDK с подробной документацией.
Получив всю техническую информацию (а производители даже выложили в открытый доступ Android-библиотеку для работы со своими маячками), он использовал программу Fiddler для слежки за HTTP/HTTPS-пакетами и их подделки.
Создав нужный сертификат, рассказчик перехватил трафик приложения и попробовал провести авторизацию запроса, введя случайный PIN. Это, конечно, не сработало. Поскольку у него не было возможности и желания брутфорсить PIN, он решил зайти со стороны маячка.
Он настроил дешёвый VPS-сервер и создал VPN-подключение для смартфона. Целью всего этого был перехват пакетов данных от маячка. Разобравшись с программной частью, он отправился в рестораны, участвующие в этой программе. Совершив покупку и получив за неё баллы, хакер перехватил нужный пакет и фактически получил возможность отправлять на сервер фальшивые пакеты с верными данными, “зарабатывая” таким образом баллы.
Полная версия с техническими деталями
Рассказывает Kuba Gretzky
Совсем недавно я наткнулся на Android-приложение, которое позволяет вам получать бесплатные закуски и напитки в различных пабах, ресторанах и кафе в обмен на баллы, выданные за прошлые покупки. Во время покупки вы говорите продавцу, что хотите получить баллы и выбираете нужный пункт в приложении. Для верификации продавец должен либо провести по устройству специальным маячком, либо ввести PIN-код.
Например, в одном из мест бесплатное пиво можно получить за 5 баллов, а за каждый купленный бокал вы получаете 1 балл, т.е. каждый шестой бокал становится бесплатным.
Все любят халявное пиво, поэтому первое, о чем я подумал — насколько защищён процесс верификации и как именно работают эти магические маячки?
И что более важно, можно ли как-то обойти защиту приложения и таки попробовать халявное пиво?
Я специально не упоминаю имя приложения, поскольку оно доступно только для моей страны (Польши). Я лишь хочу показать пример типичной уязвимости такого приложения, как их искать и как лучше защитить приложение. В этом рассказе я буду использовать вымышленное название для этого приложения — “EatApp”.
Проводим исследование
Сперва я решил разобраться в тех маячках, что используются с приложением. Очевидно, что они связываются с мобильным устройством через Bluetooth.
Совсем скоро я нашёл компанию, производящую эти маячки — Estimote, и вот что они пишут про свою технологию:
Маячки и стикеры Estimote являются маленькими беспроводными сенсорами, которые можно прикрепить к любой поверхности или предмету. Они передают радиосигналы, которые распознаются вашим смартфоном.
Похоже, приложение EatApp определяется близлежащий маячок ресторана, получает какие-то идентификационные значения и используя их для авторизации начисления баллов на сервере.
К счастью, 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:
Это поместит файл FiddlerRoot.cer на рабочий стол. Fiddler будет использовать этот файл для подделки сертификатов каждого HTTPS-соединения, проходящего через прокси. Конечно же, такие сертификаты не будут доверенными для вашего смартфона, поэтому вам нужно перенести сертификат Fiddler на ваше устройство и пометить его как надёжный.
Для этого скопируйте FiddlerRoot.cer на карту памяти вашего смартфона. Откройте Настройки > Безопасность и выберите Установить с карты памяти:
Найдите и выберите нужный сертификат. Теперь прокси будет помечен как надёжный, и вы сможете работать с HTTPS-трафиком. Убедитесь, что телефон и компьютер работают в одной сети.
Перед тем, как продолжить, выясните, какой порт слушает Fiddler, открыв Tools > Telerik Fiddler Options > Connections:
После этого выясните локальный 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. Оказалось, что моя теория была верна:
Тем не менее, мне нужно было проверить, действительно ли эти ключи использовались для авторизации, проанализировав логи 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, которая шифрует транслируемые ключи. Таким образом, для взлома понадобится корректный ключ шифрования. Для его получения опять же придётся “разобрать” приложение, а потом написать отдельное приложение для дешифровки.
- Обязательно должна присутствовать проверка сертификатов, что затруднит их подделку.
- Нельзя доверять устройству клиента! Для максимальной безопасности устройство клиента должно отправлять лишь сам запрос. После этого сервер запрашивает подтверждение у продавца, отправляя запрос на специальное устройство. Таким образом, злоумышленник никак не сможет перехватить запрос для дальнейшей подделки пакетов.
23К открытий23К показов