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

Взлом приложения
С чего всё началось
К нам пришёл клиент с популярным мобильным сервисом. Сотни тысяч установок, данные пользователей, платежи, всё как у взрослых. Запрос был спокойный. Посмотрите на безопасность, мы вроде всё закрыли, но хотим уверенности. Мы не полезли ломать сервер и подбирать пароли. Мы начали оттуда, откуда начинает реальный атакующий, и это важный момент, который большинство недооценивает. Атакующему не нужен доступ к вашей инфраструктуре, чтобы начать. Ваше приложение уже лежит в открытом магазине, и скачать его может кто угодно в мире. Мы скачали его как обычный пользователь. Здесь есть ключевое заблуждение, на котором держится половина подобных дыр. Команды думают, что приложение это запертый сейф, раз оно собрано и опубликовано. На деле это коробка со стеклянными стенками. Всё, что разработчик положил внутрь, можно достать и рассмотреть. Если у вас есть мобильное приложение или фронтенд, считайте, что его внутренности уже у атакующего. Вопрос только в том, что он там найдёт.
Что насторожило
APK или IPA это архив. Мы распаковали сборку стандартными инструментами и прошлись по содержимому методично, так же, как это делают при статическом анализе мобильных приложений. Сначала смотрели на манифест и конфигурационные файлы, потом на ресурсы, потом на сам код после декомпиляции. Интерес представляли строковые литералы и константы, всё, что разработчик мог прописать руками напрямую. В коде нашлись вещи, которых там быть не должно.
Адреса служебных эндпоинтов, не предназначенных для публики, они нигде не документировались, но спокойно лежали в бандле. Ключи доступа к стороннему облачному хранилищу. И самая весомая находка, Bearer-токен с широкими правами к основному хранилищу данных, зашитый в строковую константу прямо в коде. Кто-то из разработчиков вписал его в момент разработки, скорее всего чтобы быстро поднять интеграцию, и не убрал перед релизом. Логика была понятная: токен же спрятан внутри скомпилированного приложения, его не видно. Но видно. Декомпилятор для Android (jadx) и распаковка IPA для iOS это стандартные инструменты, и для первичного поиска секретов не нужно ничего экзотического. Строки, которые выглядят как токены, ключи или URL, находятся за несколько минут даже простым грепом по декомпилированным исходникам.
Как открылось чужое
Мы аккуратно проверили найденный токен в контролируемых условиях, не трогая реальные данные и не выполняя ничего разрушительного. Задача была одна — понять, что именно он открывает. Токен принимался без каких-либо дополнительных проверок и давал прямой доступ к хранилищу. Первым делом мы посмотрели на модель прав. Токен оказался не сервисным с узкими разрешениями под одну задачу, а широким ключом, фактически с правами на чтение большей части данных. Никакой сегрегации, никакого принципа наименьших привилегий. Второй проблемой шла адресация объектов. Идентификаторы записей в запросах оказались предсказуемыми, порядковые числа с очевидной структурой.
При этом на стороне сервера не было проверки, принадлежит ли запрашиваемая запись тому, кто её запрашивает. Это классический IDOR, Insecure Direct Object Reference, одна из самых распространённых и при этом самых болезненных по последствиям уязвимостей. Комбинация двух факторов давала полную картину. Широкий токен открывал доступ к хранилищу, предсказуемые идентификаторы без проверки владельца позволяли адресовать любую запись. То есть любой человек, скачавший приложение из официального магазина и проведший с декомпилятором час, получал путь к данным всех клиентов сервиса. Без атаки на сервер. Без перехвата трафика. Без подбора паролей. Он просто читал то, что ему выдали в руки при установке.
Где была дыра и как закрыли
Проблема была системной и состояла из трёх независимых решений, каждое из которых усиливало остальные.
Первое, секрет в клиентском коде. Правило здесь абсолютное: любые ключи, токены не должны покидать серверную сторону никогда. Мы отозвали скомпрометированный токен, перевыпустили новый и убрали все секреты из кодовой базы приложения. Приложение теперь не обращается к хранилищу напрямую, все запросы идут через собственный бэкенд, который держит ключи у себя и никогда не отдаёт их клиенту. Это меняет саму модель, секрет физически перестаёт покидать доверенную среду.
Второе, один ключ на всё. Широкий токен заменили на набор сервисных ключей с минимально необходимыми правами, по одному на каждый отдельный сценарий доступа. Теперь компрометация любого ключа означает утечку узкого кусочка, а не всей базы сразу. Это снижает цену ошибки на порядок. Третье, отсутствие проверки владельца при доступе к объектам. На каждое обращение к записи добавили серверную проверку, что текущий аутентифицированный пользователь имеет право именно на эту запись. Предсказуемые идентификаторы заменили на UUID, что само по себе убирает перебор, но главное именно серверная проверка, потому что непредсказуемый идентификатор это не защита, а просто неудобство для атакующего. После исправлений мы повторили весь путь с нуля. Свежая сборка из магазина, декомпиляция, поиск строк. Секретов в коде нет. Попытка обратиться напрямую к хранилищу без актуального токена даёт отказ. Попытка прочитать чужую запись через свой токен упирается в проверку владельца на сервере.


