Intro

Всем привет! Снова с вами Юрий Шабалин. Уже много лет я занимаюсь безопасностью мобильных приложений и в своих исследованиях доношу важность этого направления для бизнеса.

В одной из прошлых статей я составлял шорт-лист мифов о безопасности мобильных приложений. Один из них был посвящен проверке магазинами приложений перед публикацией, и сегодня мы попробуем разобраться, как с этим на самом деле обстоят дела. Некоторые из них явно обещают проводить статический и динамический анализ потенциальных уязвимостей. На словах всё красиво: код приложения анализируется, оно запускается, обнаруживаются проблемы безопасности. Но каково качество этих проверок? Могут ли они заменить пентест или ручной анализ защищенности? Давайте посмотрим!

Время приключений

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

Мы с коллегами создали такого “монстра”, и даже проверили его на OFFZONE 2023: отдали любителям решать необычные задачи, в которых участники не просто искали флаги, как в классическом CTF, а примеряли на себя роль багхантеров и писали репорты (отчеты о найденных проблемах) на найденные реальные уязвимости.

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

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

Ну что же, давайте разберемся?

Этап создания

Для эксперимента нам нужно было создать функциональное приложение, которым смогут и захотят пользоваться реальные люди. Рассматривали разные варианты, среди которых был фейковый банк, хранилище фотографий, модный AI-сканер QR-кодов. В итоге мы решили сделать приложение для хранения заметок и назвали его «Secure Notes». При создании аккаунта нужно было установить пин-код или биометрическую аутентификацию для входа – и вуаля! В нем можно было писать, редактировать и удалять заметки, импортировать их из ZIP-архива, а также добавлять к ним фотографии. Не такого ли «‎сейфа»‎ не хватало в телефоне для полного счастья и ощущения безопасности? Вот и мы подумали также!

Наше приложение по архитектуре отличалось от обычных CTF (Capture the flag), в которых вся логика расположена в Activity с говорящими названиями, содержащими в себе бизнес-логику и уязвимости. Мы придерживаемся современных практик, поэтому использовали «Чистую архитектуру» (Clean Architecture). В этом подходе структура приложения делится на слои: Presentation, Domain, Data. Presentation-слой использует MVVM-паттерн (Model-View-ViewModel). В Domain находятся Use Cases. Ну а Data отвечает за работу с хранилищами данных. В текущей реализации было решено использовать разные активности (Activity) для каждой функции (регистрации, работы с заметками и других). На схеме вы можете увидеть упрощенную схему архитектуры приложения:

Экраны приложения мы верстали с помощью XML-схем на старых добрых View. В качестве DI (внедрение зависимостей) использовали Koin, а для навигации между фрагментами – Navigation library. Взаимодействие с Backend строили на базе популярных библиотек, в том числе Retrofit. В общем, приложение создавалось в лучших традициях Android-разработки.

Что по функционалу:

  • Вход по логину, регистрация по e-mail и создание пароля

  • Установка пин-кода и биометрической аутентификации для входа (при повторном открытии приложения у пользователя появляется альтернатива)

  • На экранах регистрации и пин-кода есть кнопка «‎Новости», при нажатии на которую‎ с сервера загружаются актуальные новости

  • Основной экран приложения содержит заметки. Их можно создавать, редактировать и удалять, добавлять изображения

  • В боковом меню:
    - информация об аккаунте пользователя с возможностью редактирования;
    - смена пин-кода;
    - импорт заметок из архива (если пользователь переедет на другое устройство);
    - страница «‎Новости»;
    - лицензионное соглашение.

Цель достигнута! Приложение отдалено от классического CTF, его удобно поддерживать и обновлять – конечно, мы как добросовестные разработчики будем радовать наших пользователей апдейтами :)

Уязвимости

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

Возможность создания резервной копии данных приложения

Первая ошибка, которую допустили разработчики, это оставленная возможность создания локального бэкапа данных приложения (флаг в AndroidManifest - androidBackup=true). То есть, имея физический доступ к устройству, злоумышленник может создать резервную копию всех данных ПО и посмотреть, что же оно хранит внутри – поверьте, там очень много критичной информации. Как я уже не раз говорил, один недостаток приложения сам по себе редко опасен, а с помощью нескольких проблем можно выстроить неплохой вектор атаки на пользователя.

Исправить такую уязвимость очень просто. Для этого нужно отключить возможность создания бэкапа выставлением атрибута androidBackup=false. Более того, Google понимает, что с приходом облачных сервисов почти не пользуются этим атрибутом: он уже признан устаревшим (начиная с Android 12), а в скором времени будет вообще удален и запрещен. Несмотря на это, в 737 из 1815 приложений мы встречали подобную проблему (по данным проанализированных в рамках нового ежегодного отчета по безопасности мобильных приложений на платформе Android). 

Хранение приватных ключей

Всё так, и зрение вас не подводит. Мы оставили внутри ресурсов приложения приватный ключ, он никак не зашифрован и мог бы подходить для SSH-пользователя (Secure Shell) нашего бэкенда. Рядом положили популярный формат для хранения ключей - JKS (Java KeyStore), внутри него тоже был secret key.

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

В приложении зашит валидный ключ от стороннего сервиса с расширенными правами

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

Мы положили внутрь нашего продукта серверный ключ от Firebase Cloud Messaging (FCM). По сути это «ключ от всех дверей»‎ мира пушей, позволяющий отправить абсолютно любое PUSH-сообщение от имени приложения. Схема атаки здесь базируется на использовании «‎топиков»‎ в Firebase: можно составить условие, которое было бы всегда правдиво, и по нему отправлять сообщения всем пользователям. Например, «Отправить PUSH-уведомление всем пользователям, которые не подписаны на топик «<любой рандомный текст здесь>». Правда, сейчас эту лазейку прикрыли, отправить условие «не подписан на топик» больше нельзя, для совершения атаки придется перебирать подписки. И всё же хранение серверного ключа в клиентском коде приложения недопустимо.

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

Отсутствие обфускации

Конечно, для облегчения анализа мы не стали обфусцировать наше приложение. Наш код настолько идеальный, а названия переменных, классов и методов настолько утончены, что можно оставить их в неизменном виде. Наверное, все поняли, что это шутка, а вот в реальности всё не так смешно. По данным нашего последнего исследования, практически половина приложений (775 из 1815) пренебрегает обфускацией, что позволяет злоумышленникам быстрее находить, анализировать и использовать проблемы в коде.

Передача чувствительных данных в параметрах GET-запроса

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

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

Локальное хранение аутентификационных данных

Что будет, если приложение будет развиваться намного быстрее, чем серверная часть? Мы не отказали себе в удовольствии это проверить. Ведь быстрая аутентификация по PIN-коду это безопасно, правда же? И операционные системы надежно защищают внутреннюю песочницу нашего приложения со всеми файлами и паролями? Спойлер: нет и еще раз нет.

Для удобства мы сохранили PIN-код, логин и пароль пользователя в открытом виде с применением механизма Shared Preferences. Подобное я, к сожалению, встречал и во многих реальных приложениях. Небезопасно хранятся и данные аутентификации, и JWT-токены доступа, и сессионные идентификаторы, и паспортные данные. Свежий пример из практики: во время анализа защищенности выяснилось, что одно приложение хранило все паспортные данные пользователя в открытом виде и с возможностью бэкапа данных. Выходит, достаточно одного файла из директории приложения, чтобы получить доступ к аккаунту пользователя. 

Нередко мы видим дублирование информации, когда пароль пользователя хранится не только в Shared Preferences, но и, например, в локальной базе данных. Исправления подобных проблем носят характер «‎пофиксили там, где нашли», без проверки других уязвимых мест.

Мобильные приложения динамичнее, чем бэкенд. Зачастую они локально реализуют новый функционал, а иногда создавать новое приложение поверх старого серверного API. Эти методы небезопасны: всё, что проверяется или хранится локально, можно обойти. 

Обход биометрической аутентификации в приложении

Первое, что хочется проверить в приложении с биометрической аутентификацией, – это возможность ее обойти. Проблема в том, что приложение вместо использования шифрования или доступа к биометрии применяет метод event-bound, когда есть только API, которое посылает «true» или «false» – комментирует, верные или неверные биометрические данные предоставил пользователь. За счет этого возможно подменить возвращаемое значение и сказать приложению, что аутентификация прошла успешно, хотя на самом деле это далеко не так. 

При правильном подходе эти данные применяются сначала для шифрования на открытом ключе, приватная часть которого сохраняется уже в KeyChain / Keystore или на файловой системе устройстве. А после (если без этих данных не обойтись) – для того, чтобы получить доступ к ключу шифрования, который система хранит в наиболее защищенном месте (отдельный сопроцессор со стираемым хранилищем). Такой ключ позволит расшифровать необходимое для проверки значение. 

Еще одна проблема – это отсутствие реакции приложения на изменение биометрических данных на устройстве. Зачастую не учитывается ситуация, когда они могут быть изменены. Тогда злоумышленник при доступе к устройству может добавить свой собственный отпечаток и по нему войти в приложение без всякого знания пин-кода или пароля пользователя. 

Отсутствие защиты канала связи (SSL Pinning)

«‎Как обойти SSL Pinning в приложении?» – самый популярный вопрос в тематических чатах по мобильной безопасности. Приложение является крайне удобным способом доступа к API и переходу к анализу серверной части, но вызывает много споров и вопросов. Для чего нужен SSL Pinning:

  • Чтобы защитить клиента при попадании в недоверенную/скомпрометированную сеть или во время того, как кто-то пытается прослушать его трафик

  • Чтобы нельзя было проанализировать backend и поискать ошибки, например, в бизнес-логике (классический Security through obscurity)

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

На мой взгляд, единственная и ключевая задача SSL Pinning – защита клиента. На остальное он может влиять только косвенно. Это не серебряная пуля, а лишь способ обезопасить ваших пользователей.

Чувствительные данные в системном логе

Многие используют сообщение в логах для отладки приложений. И конечно, мы их тоже оставили в релизном билде – нужно же как-то релизные сборки тестировать. Так, пароли появились в системном журнале, к которому, начиная с Android 4.4, имеют доступ только системные приложения.

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

Использование устаревших алгоритмов шифрования 

Эта одна из ключевых проблем в мобильных приложениях. Почти в каждом мы видим использование «‎не соленого» MD5, SHA-1 и других небезопасных алгоритмов хэширования, реже по типу DES. Часто встречаются захардкоженные или сгенерированные ключи шифрования в коде, которые хранятся небезопасным способом. Мы тоже не стали изобретать велосипед и добавили «кривое‎» сохранение ключа шифрования во внутренней директории приложения. Он получался из имени пользователя и сохранялся в файлик:

Подмена фотографии в заметках через замену URI в базе данных

Как вы помните, в нашем приложении можно добавить к заметке фотографию. Ссылка на изображение хранится в базе данных в виде URI. Получив доступ к БД (об этом чуть позже), злоумышленник может может заменить URI на произвольный, тем самым подменив и картинку в заметке. Должно быть, неприятно обнаружить на месте фото любимого питомца что-то совсем неприличное…  

Возможность обновления/вставки и получения произвольных данных из БД (контент-провайдер)

В приложении реализовано несколько контент-провайдеров, которые содержат уязвимости, связанные с этим типом Android-компонентов.

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

Получение данных из произвольного файла внутри приложения (Path Traversal)

В ContentProvider серьезной и распространенной проблемой можно назвать уязвимость Path Traversal, позволяющую получить содержимое любого файла, к которому у приложения есть проход, и те самые файлы, которые в теории не могут быть доступны другим приложениям. Давайте посмотрим. Вот так легко можно получить любой произвольный файл из внутренней директории приложения, если Content Provider его путь:

Уязвимость SQL Injection

SQL Injection основываются они на тех же самых принципах, что и подобные уязвимости в Web (и эксплуатируются аналогично). Так вот, в нашем приложении провайдер com.swordfishsecurity.securenotes.provider имеет как раз такую проблему. Мы отправили запрос в провайдер примерно такого содержания:

Получили ошибку в составлении запроса и в БД в явном виде, что говорит о наличии Error-based инъекции. Хотя этот провайдер и так открыт всему миру, проще напрямую получить из него данные. Но кто знает, вдруг в следующем релизе разработчики закроют эту уязвимость?

Ошибочно экспортированный ContentProvider, утечка приватных данных

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

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

Смена пин-кода пользователя через некорректно экспортированный броадкаст-ресивер

В этом пункте заложены две проблемы. Первая – это возможность сменить пин-код без знания предыдущего. Так, злоумышленник, получивший устройство в руки, сможет быстро поставить свой пин и «‎отрезать» пользователя от его аккаунта. Вторая проблема – экспортированный броадкаст-ресивер, который принимал в себя параметр, отвечающий за установку нового пин-кода:

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

Возможность открытия произвольной Activity внутри приложения (Intent Redirection)

Уязвимость позволяет запускать внутренние неэкспортируемые Activity-приложения. Она присутствует там, где используется Intent из недоверенного источника (например, полученные из стороннего приложения с помощью методов getIntent, getParcelableExtra или onActivityResult) для запуска (startActivity, startActivityForResult) своих внутренних Activity. Другими словами, мы можем составить Intent, который внутри содержит еще один Intent с именем внутренней Activity, и наше приложение ее откроет. Реализация данной уязвимости может выглядеть так:

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

Открытие произвольной ссылки в WebView

Одним из самых простых способов взаимодействия с пользователем является DeepLink – это возможность перенаправить его в приложение по ссылке или через сайт. Для открытия URL обычно используется WebView, не требующий пересборки приложения. Это и правда удобно, но есть нюанс. На передаваемые URL редко накладывают ограничения по безопасности: чтобы домен ссылки обязательно совпадал с доменом компании, или чтобы схема была в виде https. Еще могут использовать некорректные способы защиты, например, метод contains, который проверяет присутствие domain.com. Это позволяет зарегистрировать альтернативный домен, допустим, securenotes.com.evil.com, и обойти такую проверку – domain.contains('securenotes.com').

И в нашем случае, конечно, никакой защиты на передаваемый адрес в WebView нет. Отправив любой URL в параметрах deeplink, можно увидеть его открытым внутри нашего приложения:

Получение произвольного файла из внутренней директории через WebView

Серьезную угрозу несет доступ WebView к файловой системе и отсутствие проверки на полученную схему. Злоумышленник, просто обратившись к файлу через схему file://, может завладеть его контентом. Ну а если внутри вью не отключен JS, то он легко передаст его на свой сервер. Примерный код для получения содержимого нашей внутренней директории:

И вуаля! Мы увидели, что внутри открывшейся WebView отображается содержимое файла из внутренней директории. Приложение хранит всё в незашифрованном виде, значит, получить из него можно очень много информации. А вы говорите, файл из песочницы нельзя прочитать… Можно, если знать способ.

XSS в Webview (выполнение произвольного JS-кода)

И снова проблема, связанная с отсутствием валидации схемы данных, передаваемых в параметрах URL и попадающих в WebView. Если в настройках WebView включена возможность использования JavaScript и не проверяется схема, то, выполнив следующий запрос:

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

Использование JavascriptInterface, утечка данных пользователя

Далее по списку JavaScript интерфейса – функциональность, которая позволяет вызывать нативные методы приложения напрямую из JavaScript. Это такой мостик между данными странички внутри WebView и нативным кодом приложения. Функционал, безусловно, удобный, но и очень опасный. Ведь если злоумышленнику удастся каким-то образом контролировать содержимое страницы, то он получит доступ ко многим внутренним функциям приложения. Вот так можно вызвать внутренний метод из нашего приложения:

Получили в итоге выполнения функции getUserData() данные пользователя в открытом виде.

Все перечисленные выше уязвимости, связанные с WebView, можно проэксплуатировать и при помощи DeepLink, если сделать страничку следующего содержания:

Замена произвольного файла в директории приложения (через распаковку zip-архива)

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

По уязвимости в Content Provider мы помним, что есть замечательная последовательность символов, которая позволяет нам выходить за пределы текущей директории (Path Traversal). Злоумышленник или вредоносное приложение может создать архив, который будет включать файл с путем, содержащим символы "..", например: ../shared_prefs/prefs.xml. При распаковке архива файл prefs.xml попадет в директорию shared_prefs/. А если такой файл уже есть по этому пути, то он просто перезапишется версией злоумышленника. 

Таким образом, любой “нехороший парень” может перезаписать любые файлы, доступные приложению на запись, включая файлы баз данных, бинарные *.so и *.dex. Например, при перезаписи *.so файлов возможно выполнить произвольный код в контексте приложения. Для реализации подобной атаки нужно несколько шагов:

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

  2. Подготовить нативную библиотеку, которая содержит код, выполняющий определенные действия в момент загрузки JNI_OnLoad (например, поменять права на директорию на 777 и сделать директорию доступной всем). Название файла должно совпадать с одной из библиотек, которые загружаются приложением. Пример кода:

  1. По скрипту подготовить архив, содержащий внутри написанную библиотеку

  2. При работе с приложением (или при вызове методов, которые работают с архивами, например Content Provider), вызвать функцию по распаковке созданного ранее архива

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

  4. В результате распаковки такого архива приложение заменит библиотеку внутри своей директории на библиотеку из архива

  5. При следующем запуске приложения/загрузки библиотеки будет выполнен код из библиотеки злоумышленника.

Некорректная смена пользователя (утечка информации при логауте пользователя)

Завершает наш чарт небольшой дефект, связанный с неправильной работой приложения при логауте пользователя. По логике, когда он выходит из своего аккаунта, все его данные, которые хранятся локально, должны быть очищены и удалены. А при следующем входе они должны загружаться из сервера в приложение. В нашем случае сроки поджимали, а релиз нужно уже выпускать (знакомо, правда?), поэтомы было решено оставить бэкенд на потом и реализовать хранение локально. Чтобы пользователи не теряли свои данные, мы оставляем их после логаута. Но если после выхода из аккаунта зарегистрироваться и войти уже под новым (вновь созданным) логином, то он совершенно случайно увидит заметки от предыдущего аккаунта. Так можно проверить, насколько хорошо приложение очищает свои ресурсы после выхода из аккаунта. 

Вот такие чудеса скрываются за простым функционалом нашего приложения. И это далеко не всё, что можно найти — есть еще около десяти различных проблем, которые мы заложили (а может и случайно допустили) в нем. Комбинируя эти проблемы, можно выстроить вектор атаки, который может привести к утечке данных, потере аккаунта и другим неприятностям. И в реальных приложениях такие возможности тоже есть, нужно их только увидеть. Так что нельзя рассматривать приложение как нечто отдельное — это неотъемлемая, а иногда главная часть сложной системы.

Публикация в магазины приложений

А вот и вторая часть статьи, которая является нашим исследованием относительно качества проверок на уязвимости. Сразу оговорюсь, что мы не рассматриваем анализ приложений на вредоносный код, на malware, на наличие рекламы внутри или функционала, который мог бы намеренно навредить пользователям. В большинстве “сторов” есть такой функционал и он отлично работает, не давая проникнуть откровенно зловредным приложениям в магазины (хотя и такое иногда случается). 

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

Мы обращаем внимание разработчиков (паблишеров, тех кто загружает приложения в магазины) именно на уязвимости самого приложения.

Какая у меня позиция здесь? На мой взгляд, сам магазин как площадка для распространения приложений, не обязан проводить анализ безопасности всех загружаемых в него продуктов - это очень трудоемко и весьма накладно. Безопасность приложения и пользователей - это, в первую очередь, задача разработчика и бизнеса. Но если площадка заявляет, что проверяет приложения на безопасность и выявляет уязвимости, то должна выполнять это. Чтобы не было ненужных ожиданий. 

Итак, мы создали функциональное, но крайне уязвимое приложение. Что было дальше? Нам стало интересно, насколько тщательно магазины проверяют приложения, какие требования предъявляют – в общем, как на самом деле обстоят дела с проверкой. Для этого мы выбрали наиболее крупных игроков:

  1. Google Play

  2. Huawei AppGallery

  3. Xiaomi GetApps

  4. Samsung Galaxy Store

  5. RuStore

  6. Nashstore

Поговорим о каждом из них в отдельности и посмотрим, как их заявления соотносятся с действительностью.

Google Play

Первой нашей целью и главным вызовом был, конечно же, официальный магазин Android-приложений Google Play. Он, как никто другой, должен быть заинтересован в безопасности приложений, которые скачивают миллионы пользователей. И это действительно так, как минимум, на бумаге. 

Во-первых, Google доплачивает за уязвимости, найденные в наиболее популярных приложениях. Во-вторых, у компании есть официальный чек-лист того, на что приложение проверяют перед публикацией – App security improvement program. Более того, некоторые из этих проверок блокируют публикацию уязвимого приложения.

Выдержка из статьи
Выдержка из статьи

В нашем приложении было как минимум семь уязвимостей из списка выше, с которыми нельзя публиковаться в сторах. Видим, что Google отдельно предупреждает и запрещает публиковать серверные ключи от сервиса FCM в клиентском коде приложения. Об это сказано как в программе выше, так и на странице Firebase:

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

Вот хронология событий:

  • 10 сентября. Восстанавливаем давно забытый аккаунт разработчика в Google Play, настраиваем новый проект и загружаем в него первую версию нашего приложения

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

  • 25 сентября. Наше приложение без единого замечания попадает в Google Play!

  • 20 октября. До нас добирается сканер Google и оставляет нам два замечания в консоли разработчика.

На этом история заканчивается. Из всех проблем, которые Google обещает проверять, в нашем приложении он нашел только две мелочи, не мешающие публикации. Ни огромное количество инъекций, ни серверный ключ FCM нового образца в ресурсах приложения их нисколько не смутили. Можно смело делать вывод, что приложение не проверяется на безопасность перед публикацией, а пост-проверки по качеству оставляют желать лучшего. И это при том, что они открыто говорят о том, что блокируют приложения с уязвимостями.

Huawei AppGallery

Следующим крупный магазин, особенно после того, как компанию Huawei "отлучили" от возможности использования Google Services на своих устройствах, – Huawei AppGallery. К их чести, в своей документации в секции про безопасность они говорят только о том, что приложение не должно иметь скрытый функционал или нарушать конфиденциальность – всего 19 условий. Но в некоторых материалах можно заметить, что они все-таки проводят анализ приложений, в том числе и на уязвимости.

В надежде, что этот магазин покажет себя более подкованным с точки зрения анализа, мы начали загружать наше приложение. Кстати, для публикации в AppGallery из КНР нужно обязательно приложить отчет о безопасности и проверке на уязвимости и недопустимый контент: от правительственной организации, от сторонней компании или внутренний. Всем бы так!

Итак, хронология событий:

  • 3 сентября. Регистрация в консоли разработчика, подтверждение личности

  • 25 сентября. Публикация первой версии приложения

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

  • 2 октября. Наше приложение успешно опубликовано

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

После публикации около нашего приложения гордо красовались надписи об успешном прохождении проверки: «На рекламу», «На вирусы» и «Ручная проверка». Тем не менее, никаких уведомлений о проблемах с безопасностью за прошедший месяц нам от Huawei не поступало.

Xiaomi GetApps

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

  • 25 сентября. Регистрация аккаунта, подтверждение данных

  • 27 сентября. Отправка приложения на публикацию

  • 1 октября. Успешная публикация приложения

Xiaomi максимально честны в этом плане - нет информации и нет проверки. Magnifique!

Samsung Galaxy Store

С этим магазином вышла самая интересная история. Galaxy Store очень красочно описывает, как разбирает приложения «по косточкам» и детально проверяет его на всех уровнях. В прошлый раз, когда я искал информацию о том, какие проверки есть в магазинах, я находил целый документ, который в картинке “пирамидке” показывал, что Samsung использует 4-х ступенчатый анализ приложений. Но на момент написания статьи я так и не смог ее найти (в случае, если найду, обязательно дополню статью). Но зато есть вот такой процесс в Galaxy Store:

Что мы сделали, и что у нас получилось:

  1. Создали аккаунт компании. Samsung попросил указать международный номер фирмы или создать частный профиль

  2. Сделали частный аккаунт. Магазин потребовал заявку на то, что мы частный «продавец»

  3. Отправили заявку, что мы частный «продавец». Барабанная дробь! Нас попросили сделать аккаунт компании

  4. Для нового аккаунта компании запросили международный номер. Вот такой замкнутый круг

Итог: С 1 декабря 2022 года Galaxy Store требует от всех продавцов приложений для Android зарегистрироваться в качестве корпоративных коммерческих продавцов. Без номера DUNS нельзя выпускать приложения в сторе, а на момент публикации приложения у нас его не было. Как только у нас появится этот номер, или кто-то будет готов нам его одолжить, мы обязательно загрузим туда наше приложение. Хотя, даже имея на руках номер DUNS, кажется, что только загрузка займет кучу времени – настолько неудобный там интерфейс личного кабинета разработчика.

RuStore и NashStore

Мы также загрузили свое приложение в отечественные магазины RuStore и NashStore.
Публикация в обоих сторах заняла немного времени. Личный кабинет создается быстро и без проблем.

С RuStore получилось самое быстрое и, наверное, самое приятное взаимодействие:

  • 14 сентября. Отправили заявку на личный кабинет. При этом мне пришлось пройти проверку через ГосУслуги с ЭЦП и еще мне дополнительно написали, чтобы удостовериться, что я тот за кого себя выдаю.

  • 14 сентября. Отправили заявку на публикацию приложения. Это единственный из магазинов, который потребовал обоснование, для чего нам необходимы разрешения для записи и чтения с внешнего хранилища.

  • 15 сентября. Приложение опубликовано.

Но как я говорил ранее, проверки на уязвимости не были заявлены как часть обязательного процесса, поэтому и вопросов нет.

В NashStore публикация также прошла быстро и без единой проверки:

  • 15 сентября. Пройдена регистрация и подана заявка на публикацию

  • 27 сентября. Приложение появилось в магазине

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

Outro

Название магазина

Причины отказов в публикации

Было ли опубликовано приложение?

Ссылка на приложение

Google Play

-

Да

https://play.google.com/store/apps/details?id=com.swordfishsecurity.securenotes

Xiaomi GetApp

-

Да

https://appgallery.huawei.com/#/app/C109264147

Huawei AppGallery

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

Да

https://appgallery.huawei.com/#/app/C109264147

RuStore

Запросили подтверждение личности

Запросили обоснования некоторых разрешений в приложении

Да

https://apps.rustore.ru/app/com.swordfishsecurity.securenotes

Nashstore

-

Да

Надо скачать приложение магазина и уже в нем найти приложение «Безопасные заметки»

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

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

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

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

Надеюсь, вам понравился наш небольшой эксперимент, и буду рад ответить на все вопросы в комментариях. Тех, кому интересна тема уязвимостей в мобильных приложениях, приглашаю в мой TG-канал @mobile_appsec_world – там регулярно выходят новости, обзоры на инструменты и исследования по мобильной безопасности!

С вами был Юрий Шабалин. Всем хорошей недели и до новых встреч!

А я в отпуск ;)

Комментарии (6)


  1. iamoblomov
    16.11.2023 09:03

    Не, я подозревал что там все плохо, но чтобы так...

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

    Ну и в андроиде нынче приложения оч жестко придушены. Их убивает из памяти, запрещает автозапуск и тд.


  1. iamoblomov
    16.11.2023 09:03
    +1

    Попробуйте теперь начать писать репорты им что там уязвимости, и посмотреть как отреагируют


  1. Tirarex
    16.11.2023 09:03

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


  1. itmind
    16.11.2023 09:03

    Еще нужно также проверить Apple AppStore


  1. 402d
    16.11.2023 09:03

    Хмм! Ходит байка про 100к установок в гугл плей. Многих банили после достижения этой цифры. В магазине 3м приложений. Каждый год +- 200-300 тысяч приложений.

    А вот планку 100к установок взяли только 200 тысяч ( менее 7%).

    Я все таки скланяюсь к версии, что там есть приоритет к кому внимательней присматриваться


  1. 402d
    16.11.2023 09:03

    @Mr_R1p, что еще хочу заметить. Сейчас посмотрел в своей консоле.

    Число устройств, на которых проверяют за 5 лет выросло с 4-5 в начале до 10. Не знаю у всех так или нет. Хотел сравнить приложения с разным MAU / числом инсталов.

    Но к другие приложения я просто релизил. И у них

    Возможно гугл затыкает дырку без прохождения тестов как раз из-за описанной вами ситуации.

    https://support.google.com/googleplay/android-developer/answer/14151465?ref=vc.ru

    То есть теперь автотесты отрабатывать будут для всех и еще нужно будет искать 20 человек.