Написать пост

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

Аватар Иван Бирюков

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

К счастью, 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-библиотеку для упрощения разработки приложений. Вот один из примеров настройки слушателя маячка:

			beaconManager = new BeaconManager(getApplicationContext());  
// add this below:
beaconManager.connect(new BeaconManager.ServiceReadyCallback() {  
    @Override
    public void onServiceReady() {
        beaconManager.startMonitoring(new Region(
                "monitored region",
                UUID.fromString("B9407F30-F5F8-466E-AFF9-25556B57FE6D"),
                22504, 48827));
    }
});
		

Я подумал, что 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:

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

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

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

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

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

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

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

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

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

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

Ловим трафик

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

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

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

			POST https://api.eatapp.com/users/461845f5d03e6c052a43afbc/points HTTP/1.1  
Accept: application/json  
Accept-Language: en-us  
X-App-Version: 1.28.0  
User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.4.4;)  
...
Content-Type: application/json; charset=UTF-8  
Content-Length: 265  
Host: api.eatapp.com  
Connection: Keep-Alive  
Accept-Encoding: gzip

{
  "authentication_token":"boKUp9vBHNAJp7XbWZCK",
  "point":{
    "promoted_products_ids":[
      {"id":"760493597149625959620000"},
      {"id":"760493597149625959620000"}
    ],
    "pin":"1234",
    "place_id":"6088",
    "isDoneByGesture":false
  },
  "longitude":0.0,
  "latitude":0.0
}
		

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

			HTTP/1.1 422 Unprocessable Entity  
Server: nginx  
Date: ...  
Content-Type: application/json; charset=utf-8  
Content-Length: 99  
Connection: keep-alive  
Vary: Accept-Encoding

{
  "status":"422 Unprocessable Entity",
  "code":"incorrectArgument",
  "title":"Incorrect argument: pin."
}
		

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

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

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

			HTTP/1.1 422 Unprocessable Entity  
Server: nginx  
Date: ...  
Content-Type: application/json; charset=utf-8  
Content-Length: 289  
Connection: keep-alive  
Vary: Accept-Encoding

{
  "status":"422 Unprocessable Entity",
  "code":"pinTooManyAttempts",
  "title":"Too many pin code attempts",
  "header":"Account locked",
  "message":"Earning and spending points and redeeming deals using your account has been locked for the next 30 minutes. Please let us know if this is a mistake."
}
		

Если бы у меня были несколько сотен аккаунтов, между которыми я мог бы переключаться во время брутфорсинга, то 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 при помощи автоматических скриптов. Вот всё, что мне пришлось сделать на сервере:

			wget https://git.io/vpnsetup -O vpnsetup.sh  
nano -w vpnsetup.sh  
[Replace with your own values: YOUR_IPSEC_PSK, YOUR_USERNAME and YOUR_PASSWORD]
sudo sh vpnsetup.sh
		

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. Установка была лёгкой:

			wget http://mirror.roe.ch/rel/sslsplit/sslsplit-0.5.0.tar.bz2  
tar jxvf sslsplit-0.5.0.tar.bz2  
cd sslsplit-0.5.0  
make  
sudo make install
		

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

			iptables -F  
iptables -t nat -F  
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE  
iptables -t nat -A POSTROUTING -o ppp+ -j MASQUERADE  
iptables-save > /etc/iptables.rules
		

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

			echo 1 > /proc/sys/net/ipv4/ip_forward
		

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

			mkdir sslsplit  
mkdir sslsplit/certs  
mkdir sslsplit/logs  
cd sslsplit/certs  
openssl genrsa -out ca.key 2048  
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt
		

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

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

/root/sslsplit/start.sh

			#!/bin/bash
iptables-restore < /etc/iptables.rules  
iptables -t nat -A PREROUTING -i ppp+ -p tcp --dport 80 -j REDIRECT --to-ports 8080  
iptables -t nat -A PREROUTING -i ppp+ -p tcp --dport 443 -j REDIRECT --to-ports 8443  
sslsplit -d -l /root/sslsplit/connections.log -j /root/sslsplit/ -F /root/sslsplit/logs/%T -k certs/ca.key -c certs/ca.crt ssl 0.0.0.0 8443 tcp 0.0.0.0 8080
		

/root/sslsplit/stop.sh

			#!/bin/bash
killall sslsplit  
iptables-restore < /etc/iptables.rules
		

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

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

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

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

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

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

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

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

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

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

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

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

			pip install http-parser  
python splitparse.py -i [sslsplit_logs_dir] -o [output_dir]
		

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

			POST /users/461845f5d03e6c052a43afbc/points  
Accept: application/json  
Accept-Language: en-gb  
X-App-Version: 1.28.0  
User-Agent: Dalvik/2.1.0 (Linux; U; Android 6.0.1;)  
...
Content-Type: application/json; charset=UTF-8  
Content-Length: 375  
Host: api.eatapp.com  
Connection: Keep-Alive  
Accept-Encoding: gzip

{
  "authentication_token":"boKUp9vBHNAJp7XbWZCK",
  "latitude":...,
  "longitude":...,
  "point":{
    "isDoneByGesture":false,
    "main_beacon":{
      "major":38995,
      "minor":12702,
      "uuid":"2C75E74B-41B7-49E3-BD26-CE86B2F569F8"
    },
    "place_id":"450",
    "promoted_products_ids":[
      {"id":"647035946536601578040000"},
      {"id":"647035946536601578040000"},
      {"id":"647035946536601578050000"}
    ]
  }
}
		

Джекпот! Я убедился, что найденные в пакете 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К показов