Как стать автором
Обновить
347.09
TINKOFF
IT’s Tinkoff — просто о сложном

Безопасная передача данных между двумя приложениями

Время на прочтение5 мин
Количество просмотров9.1K
Всем привет, сегодня я хотел бы вам рассказать о некоторых вариантах передачи данных между двумя андроид приложениями и рассмотреть их с точки зрения безопасности. Я решил написать эту статью по двум причинам. Первая — я начал часто сталкиваться с непониманием разработчиков механизмов работы компонентов андроид приложения. Вторая — я перестал понимать на чем основан выбор того или иного механизма при реализации фич и захотел донести, как он должен выглядеть на минималках.

Задача

У нас есть 2 приложения, которые обращаются к одному API. Клиенты могут обращаться в API по токену доступа (sessionId). Необходимо реализовать бесшовный переход из одного приложения в другое. Для этого между ними нужно что-то пошарить, для примера пусть это будет sessionId.

Вариант #1: QUERY DEEPLINK

Самый очевидный вариант — передавать токен в Query DeepLink. Выглядеть это будет примерно так:
slave://main?sessionId=686A885A4FB644053C584B9BE2A70C7D
В этом случае реципиент сможет извлечь sessionId и пользоваться им, не запрашивая у пользователя авторизацию. Со стороны разработчика выглядит, что задача выполнена, но давайте копнем немного глубже.

DeepLink Hijacking


Поскольку любое приложение может зарегистрировать схему tinkoff://, OS может открыть не то приложение. Это возможно из-за того, что нет никакой регистрации и ограничений на использование схем. Вредоносное приложение может зарегистрировать схему tinkoff:// и перехватить запрос к приложению Tinkoff и запустить себя. В этом случае sessionId попадет к злоумышленникам, и ваш аккаунт будет скомпрометирован. Кроме того, DeepLink Hijacking позволяет проводить фишинг, например, отображая поля для ввода логина и пароля.

Концептуально процесс выглядит так:

image

Есть 2 решения этой проблемы. Первое — AppLinks-технология больше не позволяет разработчикам настраивать схему, вместо этого используются http/https. В этом случае OS берет ссылку slave.com/profile и связывается с хостом slave.com для проверки. Второе — Intent URL — вместо вызова slave:// вызывается intent://, куда передается уникальный идентификатор приложения, которое нужно запустить. Выглядит это так:

intent://main/#Intent;scheme=slave;package=com.example.slave.client.android;end"

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

Session Fixation


Это атака, при которой злоумышленник вынуждает клиента установить сеанс с целевым ПО, используя sessionId, предоставленный злоумышленником. Как только пользователь аутентифицируется, злоумышленник сможет использовать этот, уже привилегированный, идентификатор в своих целях. Атака использует тот факт, что целевое ПО использует тот же sessionId после повышения привилегий.


Как это выглядит в нашем случае:

  1. злоумышленник получает анонимную сессию у приложения
  2. кидает красиво письмо жертве от лица банка, в котором ему предлагается перейти в личный кабинет
  3. при переходе по ссылке мы попадаем на DeepLink с сессией злоумышленника slave://main?sessionId=686A885A4FB644053C584B9BE2A70C7D
  4. мобильное приложение берет сессию, понимает, что у нее не хватает прав и просит пользователя пройти аутентификацию
  5. пользователь ее проходит, у сессии повышаются права
  6. пользователь в приложении, злоумышленник с привилегированной сессией, профит

Правильно было бы это пофиксить на API, выдавая другой sessionId после повышения привилегий, но мы пишем мобильное приложение. И наш путь — это отказаться от передачи токена от master к slave. Плюс, это даст нам защиту в глубину и, если на API что-то сломается, и токены не будут меняться при повышении привилегий, то атака все равно будет невозможна.

3rd Party Leakage


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

Вариант #2: CONTENT PROVIDER

Как будем делать? Определим у master Content-Provider и заставим slave сходить в этот Content-Provider за токеном.



Таким образом, мы избавляемся от риска передачи не тому приложению токена в случае DeepLink Hijacking и делаем невозможным атаку Session Fixation. Но у нас появляются другие проблемы — в текущем варианте вообще любое приложение может запросить токен в любой момент времени, даже если мы не инициировали его запуск.

Protection Level


В большинстве случаев вам нужно проверить, что slave подписано тем же ключом, что и master, то есть принадлежат одному автору. Для этого случая в менеджере пакетов есть метод checkSignatures, который проверяет подписи приложений. Чтобы воспользоваться этой функцией необходимо добавить permission с protectionLevel=«signature» у Content-Provider в манифесте приложения:

<permission
  android:name="com.example.contentprovider.access"
  android:protectionLevel="signature"/>
<application>
  <provider
    ...
    android:readPermission="com.example.contentprovider.access">
</application>

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

Permission Race Condition


Есть одна очень неприятная особенность в том, что имена у permission не уникальные, чем может воспользоваться вредоносное приложение и зарегать permission с нашим именем и protectionLevel=«normal» до нас. В этом случае, при установке нашего приложения, в OS уже будет существовать permission и он не перезапишется. Следовательно, наш content-provider останется без защиты и с разрешенным доступом от любого приложения.

Different Signatures


К сожалению, далеко не всегда приложения подписаны одним ключом, например, какое-то из приложений куплено, или «так исторически сложилось», но бесшовный переход все равно нужен. В этом случае мы берем проверку подписи на себя.
Как это можно реализовать:
У Content-Provider есть метод getCallingPackage(), по которому мы можем получить packageId обратившегося за данными приложения, а по packageId можем получить список подписей и проверить их с встроенными.

String pkg = this.getCallingPackage();
PackageInfo pkgInfo = pkgmgr.getPackageInfo(pkg, GET_SIGNATURES);
Signatures[] signatures = pkgInfo.signatures;
for (Signature sig: signatures) {
  if (sig.equals(TRUSTED_SIGNATURE)) {
    // trusted signature found, trust the application
  }
}



Кажется, что мы сделали все идеально, но нет.

Fake id vulnerability


Проблема заключается в том, что когда Android создает chain-of-trust, процесс проверки сравнивает только subject, а не проверяет подпись в поле certificate’s signer. В результате злоумышленник может построить chain-of-trust без фактической подписи.

Из-за этой ошибки генерируется неправильная цепочка сертификатов, которая может включать в себя легитимные сертификаты, встроенные в APK, но фактически не использовавшиеся для подписи приложения. В конце оставлю ссылку на коммит, который исправляет данную уязвимость. Проблема исправлена в версии android 4.4, так что нам остается повысить API Level до 19.

Выводы

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

Всем безопасных приложений!

Ссылки

Теги:
Хабы:
+15
Комментарии3

Публикации

Информация

Сайт
www.tinkoff.ru
Дата регистрации
Дата основания
Численность
свыше 10 000 человек
Местоположение
Россия