Поговорим про безопасность в Dart и Flutter

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

Безопасность приложения определяется всеми уровнями - от операционной системы и компилятора до используемых пакетов/плагинов и кода самого приложения. Особенно этот вопрос актуален, когда значительная часть используемых компонентов поддерживается сообществом и не контролируется единой организацией или фондом и, чем более популярной становится платформа, чем больше пакетов появляется и чем больше становится кодовая база платформа, тем больше вероятность возникновения уязвимостей разного уровня (особенно в низкоуровневом коде на C++, где возможна утечка памяти, переполнение буфера, состояние гонки и другие неприятности), а также внедрения вредоносного кода разработчиками из сообщества (начиная от безобидных баннеров, до внедрения шпионского кода, бэкдоров и деструктивных функций). В статье мы обсудим какие векторы атак возможны в Dart, какие меры предпринимает сообщество и Google для снижения рисков при создании платформы и пакетов (и про бейджик openssf), и как можно обезопасить себя.

Начнем с самого низкого уровня и посмотрим прежде всего на компилированный код, который может запускаться как независимый исполняемый образ (при использовании AOT-компиляции), либо передаваться на исполнение виртуальной машине (в JIT, Kernel или Dill-образах). В первом случае создается код, скомпилированный из нашего приложения и исходных текстов используемых пакетов/плагинов, собранный с компонентами платформы (только используемые функции после tree-shaking) и нативным кодом плагинов. Во втором случае исполнение происходит внутри среды исполнения, при этом код представлен в виде операторов и их параметров (своеобразного байт-кода), в большей или меньшей степени напоминающих исходный код приложения. Потенциальные уязвимости могут быть в реализации используемых пакетов из Dart SDK (ссылка на runtime, реализован на C++) и обновляемый список уязвимостей Dart SDK можно посмотреть здесь или здесь (при этом атак, нацеленных на повышение привилегий в них сейчас нет). В целом приложение запускается в относительно изолированной среде выполнения (которую обеспечивает механизм изолятов) со своим управлением памятью и сборщиком мусора (здесь потенциальной угрозой может быть только переполнение памяти или запуск нагружающих процессор алгоритмов), но существует два сценария, когда приложение выходит за рамки сетевых взаимодействий и операций с файлами:

  • FFI (Foreign Function Interface) - вызов нативного кода (код будет скомпилирован и слинкован с приложением на этапе сборки);

  • использование Dart Embedding API, когда Dart-приложение встраивается во внешнее приложение и вызывается из него;

Поскольку внешний код никак не контролируется, он может вызвать проблемы с утечкой памяти, попытками выхода из userspace на уровень ядра системы, эксплуатацией уязвимостей процессора или видеоадаптера и прочие проблемы, характерные для нативных приложений. Особенную сложность представляет ситуация, когда внешний код запускает сетевой сервис (например, D-Bus клиент) и при неправильном использовании это может привести к эксплуатации уязвимости переполнения буфера и исполнения вредоносного кода. Непосредственно в Dart такой ситуации возникнуть не может, поскольку данные сохраняются в динамической памяти и отделены от исполняемого кода.

Но при этом важно помнить, что приложение выполняется в операционной системе от имени пользователя, который его запустил (или от владельца файла, если указан suid) и доступ к файлам и устройствам ввода-вывода, а также выделение памяти и процессорного времени регулируется механизмами операционной системы, поэтому (если не будет эксплуатироваться известная уязвимость системы или системного сервиса, что наиболее актуально для desktop-приложений) доступ к файлам будет ограничен правами пользователя (чаще всего модификация файлов запрещена везде, кроме домашнего каталога и каталога временных файлов). При этом dart:io представляет полный доступ к файлам на Desktop-системах и это потенциально может быть использовано во враждебных целях. На мобильных платформах доступ к чувствительной информации и сервисам регулируется разрешениями доступа.

Поскольку мы не контролируем весь Dart-код и подключаем к нашему приложению как минимум стандартную библиотеку, а в большинстве случаев еще и внешние пакеты или плагины (с платформенным кодом), то нужно быть уверенными, что автор пакета не добавит вредоносный код при обновлении. Самый простой способ избежать этой проблемы - указывать полную версию пакета (не использовать обобщения * или пустую строку, а также интервалы версий). При этом нужно отслеживать (вручную или автоматически) обнаружение уязвимостей в используемых пакетах и их зависимостях, чтобы своевременно установить обновление. Например, для этого может использоваться dependabot, который теперь поддерживает pub-репозитории (подробнее здесь).

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

  • перехват сетевого трафика и сравнение с эталонным - здесь может быть полезно использовать mitmproxy - свободный инструмент, который может не только перехватывать запросы и ответы (в том числе, по https с использование собственного сертификата), но и обрабатывать сессию с целью поиска неожиданных запросов (для этого можно использовать Python API);

  • перехват сообщений, которые отправляются в консоль - это можно осуществить через создание зоны с переопределенной функцией print, например runZoned(() { runApp(); }, zoneSpecification: ZoneSpecification(print: (Zone self, ZoneDelegate parent, Zone zone, String line) { parent.print(zone, "Intercepted: $line"); }));. В реализации print можно добавить проверку на наличие ключевых слов или обнаружение сообщений с необычного префикса (или без префикса);

  • отслеживание доступа к файлам - для этого может использоваться watcher (работает поверх inotify или собственных механизмов операционной системы), с помощью него можно обнаружить операции доступа к файлам в указанном местоположении (например, в каталоге данных приложения или в документах пользователя);

Также можно использовать инструменты статического анализа (они также позволяют обнаружить потенциальные уязвимости и плохие практики в собственном коде). Кроме встроенного анализатора, который отлично справляется с обнаружением неудачных фрагментов кода на Dart и Flutter, можно посмотреть в сторону Horusec - инструмента статического анализа, который (кроме множества других языков) поддерживает и Dart. Он может быть установлен локально (с веб-интерфейсом для просмотра обнаруженных уязвимостей) с Docker Compose или через Helm в кластер Kubernetes. Также его можно добавить в конвейер сборки, наряду с другими тестами.

Чтобы обезопасить свой продукт, нужно быть уверенным, что ни на одном этапе доставки кода риски внедрения вредоносного кода будут минимальными и весь процесс будет автоматизирован (чтобы исключить атаку supply chain, когда может быть внедрена уязвимость на любом уровне - начиная от внешних пакетов/плагинов и заканчивая процедурой доставки обновления до устройств пользователей). Это может быть достигнуто через ограничение возможности отправки изменений в мастер-ветку, peer-review, контроль версий устанавливаемых пакетов и др, и этот процесс тоже можно автоматизировать. Основные репозитории Dart и Flutter сейчас проходят автоматизированную процедуру проверки на соответствие требованиям безопасности OpenSSF (Open Source Security Foundation), результатом которого является отчет о возможных слабых местах в процессе управления зависимостями, оценки качества кода, управления ветками в системе контроля версий, сборки и публикации приложения. Инструмент анализа может быть запущен как Docker-контейнер (нужно передать Github-токен, поскольку забирается большое количество репозиториев для выполнения шагов анализа):

docker run -e GITHUB_AUTH_TOKEN=ghp_<token> gcr.io/openssf/scorecard:stable\
           --show-details --repo=https://github.com/flutter/flutter

Для проверки может использоваться любой репозиторий (--repo) или локальный каталог (--local). По умолчанию запускаются все проверки, в том числе на фиксирование версий зависимостей, наличие код-ревью, защита веток от отправки без ревью, отсутствие двоичных артефактов и другие, но список проверок может быть сокращен (--check).

Мы поговорили про возможные проблемы и способы их избежания для приложений на Dart, теперь перейдем к фреймворку Flutter. Кроме всего вышесказанного, нужно отметить еще несколько возможных областей нарушения безопасности, связанных с архитектурой фреймворка и особенностями хранения данных приложений при использовании SQLite и Shared Preferences (они не шифруются и могут быть прочитаны при наличии прав разработчика и root). Также при наличии root могут быть применены hook для переопределения реализации (например, так может быть обойдена проверка на наличие root-прав).

  • переопределение системных каналов - Flutter Engine регистрирует множество платформенных каналов для передачи сообщений и вызовов методов при изменениях в жизненном цикле приложения, событиях взаимодействия пользователя с экраном и устройствами ввода и других событиях платформы и определяет их по имени, на которые подписываются core-классы фреймворка. Здесь может быть потенциальная проблема в плагинах при переопределении источника платформенного канала или подписки на них для фиксации низкоуровневых событий (например, с целью отправки нежелательной аналитики), но простого способа обнаружить это без анализа исходных кодов нет, каналы не предоставляют необходимых методов для определения источника и других подписчиков (для EventChannel);

  • прямой доступ к объекту ui:window или использование Overlay для отображения элементов поверх экрана приложения (рекламы или баннеров);

Также для дополнительной защиты сгенерированного образа приложения можно использовать обфускацию (для усложнения декомпиляции кода, скрывает исходные названия классов, методов, переменных и затрудняет понимание алгоритма при дизассемблировании) через флаг --obfuscate в flutter build, а также хранить строковые ресурсы в отдельном файле (как asset) в зашифрованном виде (например, можно использовать библиотеку flutter_sodium).

Для обнаружения потенциальных проблем можно использовать следующие решения:

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

bool findWidget(Element element, Type T) {
  if (element.widget.runtimeType == T) return true;
  bool found = false;
  element.visitChildren((el) {
    if (findWidget(el, T)) found = true;
  });
  return found;
}

bool detectWidget(Type T) {
  WidgetsBinding.instance.addPostFrameCallback((_) {
    if (findWidget(WidgetsBinding.instance.renderViewElement!, OverlayEntry)) {
      //сюда мы попадем, если на экране есть лишний OverlayEntry
    }
  });
}
  • обнаружение дополнительных разрешений (для Flutter-приложений), подозрительных фрагментов кода или наличия чувствительной информации - для этого можно использовать Mobile Security Framework, который работает с уже собранными приложениями (apk / ipa), обнаруживает трекеры (по наличию ссылок или фрагментов кода), а также определяет индекс безопасности приложения (с учетом разрешений, использования возможностей системы и других эвристик);

  • проверять приложение на соответствие исходному образу (например, использовать пакет flutter_security);

  • для избежания риска утечки данных можно проверять устройство на наличие взлома (jailbreak для iOS, root для android), можно использовать flutter_jailbreak_detection;

  • использовать in-app решения для анализа уязвимостей (например, freerasp от Talsec);

  • проверить на наличие небезопасного хранения чувствительной информации в своем коде или в коде пакетов (shared_preferences без шифрования для пин-кодов, токенов авторизации и др.)

  • проверить на использование SSL/TLS при взаимодействии с сервером (и на установку безопасных шифров и использование SSL Pinning для исключения подмены сертификата)

Мы рассмотрели несколько возможных угроз в Dart/Flutter приложении и инструменты, которые могут использоваться для их обнаружения и уменьшения вероятности попадания уязвимого кода в production. Конечно же, сейчас количество инструментов статического анализа не очень велико, но ситуация постепенно улучшается и безопасности открытого кода уделяется все больше внимания (например, в рамках работы Open Source Security Foundation). Приглашаю всех обсудить в комментариях, какие еще возможные векторы атак и уязвимости вы видите, и какие инструменты были бы полезны для сохранения Flutter-экосистемы и приложений безопасными.


В новой версии Flutter 3.0 была добавлена официальная поддержка игровых движков Flare и SpriteWidget и набор инструментов CasualGamingKit. Хочу пригласить вас на бесплатный урок, где мы изучим возможности Flutter для создания кроссплатформенных игр для мобильных устройств, веб и настольных компьютеров и создадим простую аркадную игру от начала и до подготовки к публикации в магазинах приложений.

  • Подробнее о бесплатном уроке

Источник: https://habr.com/ru/company/otus/blog/676138/


Интересные статьи

Интересные статьи

Всем привет, читатели Habr! В этой статье я расскажу как ускорить работу и облегчить нам жизнь с помощью VS Code Tasks. Погнали!Мы пишем не только кодЭто правда, разработчики пишут  не только код...
Авторизация по лицу или по отпечаткам пальцев повышает безопасность устройств. Об этом в своем блоге пишет Трой Хант (Troy Hunt), создатель ресурса для проверки утечек данных Haveibeenpwned, отвечая н...
Предисловие Почти полтора года назад мы выпустили первую версию библиотеки "Новый пакет валидаций для React на Mobx @quantumart/mobx-form-validation-kit" Время шло, и библиоте...
Помнится, в далеком детстве мне подарили микроскоп «Натуралист» – игрушечный, но таки дающий фиксированное увеличение аж в шестьдесят раз. Состоял он из одной трубки, закрепляемой на плас...
В статье описаны необходимые параметры сервера для оптимальной работы сайта на платформе 1С-Битрикс.