Введение

Всем привет! На связи Юрий Шабалин и Веселина Зацепина ( @VeselinaZatsepina ) эксперты по безопасности мобильных приложений в компании Стингрей. С каждым годом мобильные приложения становятся всё более сложными и взаимосвязанными, предлагая пользователям бесшовный опыт взаимодействия. Одной из ключевых частей этого опыта являются ссылки, которые могут направить пользователя прямо на определённый экран приложения или на конкретный контент. Однако многие (как и мы до того, как написать эту статью) путают такие термины, как Deep Links, Web Links и App Links, что может привести к ошибкам в реализации и уязвимостям.

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

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

Какие бывают типы ссылок?

Прежде чем углубляться в безопасность и проблемы, важно понять, какие вообще бывают типы ссылок (links), которые мы можем создать в приложении Android:

 - Deep links

 - Web links

 - Android App Links

На рисунке 1 показана взаимосвязь между этими типами ссылок. Далее мы разберём каждый тип немного подробнее.

Рис. 1. Взаимосвязь между типами ссылок
Рис. 1. Взаимосвязь между типами ссылок

Deep links

Deep Links — это URI любой схемы, которые позволяют направить пользователя в конкретную часть приложения. Такие ссылки могут выглядеть, например, как myapp://path или superapp://path/to/resource. Чтобы реализовать поддержку Deep Links в Android, необходимо добавить соответствующий intent-filter в манифест приложения, который определит, какая Activity будет обрабатывать данную ссылку.

Вот пример кода, реализующего Deep Link:

<activity
    android:name=".ui.MyActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />

        <data
            android:host="myhost"
            android:scheme="myscheme" />
    </intent-filter>
</activity>

Когда пользователь нажимает на Deep link, может появиться диалоговое окно "устранения неоднозначности". Оно возникает, когда несколько приложений на устройстве зарегистрированы для обработки одной и той же ссылки. Диалоговое окно позволяет пользователю выбрать нужное приложение для открытия ссылки. Оно обеспечивает свободу выбора для пользователя, однако может создавать неудобства и риски, если он выберет ненадежное приложение. Избежать появления этого окна можно, используя App Links и подтверждая право собственности на домен, что делает ваше приложение обработчиком по умолчанию для конкретного типа ссылок. Но об этом чуть позже.

На рисунке 2 показано диалоговое окно после того, как пользователь нажимает на ссылку.

Рис. 2. Диалоговое окно выбора приложений
Рис. 2. Диалоговое окно выбора приложений

Web links

Web Links — это ссылки, которые используют стандартные схемы HTTP и HTTPS и ведут к веб-контенту. На устройствах с Android 12 и выше нажатие на такую ссылку всегда открывает её в браузере, если она не привязана к конкретному приложению (то есть, если приложение не зарегистрировано как обработчик этой ссылки в системе, что позволяет Android автоматически передавать управление ссылкой этому приложению).

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

В следующем фрагменте кода показан пример объявления Web link:

<activity
    android:name=".ui.MyActivity"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />

        <data
            android:host="http"
            android:scheme="mydomain.com" />
    </intent-filter>
</activity>

Android App Links

App Links, доступные на Android 6.0 (уровень API 23) и выше, представляют собой Web links, которые используют схемы HTTP и HTTPS и содержат атрибут autoVerify. Этот атрибут позволяет нашему приложению назначить себя обработчиком по умолчанию для определенного типа ссылки. Поэтому, когда пользователь нажимает на App Link, наше приложение открывается немедленно, если оно установлено, и диалоговое окно "устранения неоднозначности" не отображается.

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

В следующем фрагменте кода показан пример объявления App Link:

<activity
    android:name=".ui.MyActivity"
    android:exported="true">
    <intent-filter android:autoVerify="true">
         <action android:name="android.intent.action.VIEW" />
         <category android:name="android.intent.category.DEFAULT" />
         <category android:name="android.intent.category.BROWSABLE" />

         <data android:scheme="http" />
         <data android:scheme="http" />
         <data android:host="myhost.com" />

 </intent-filter>
</activity>

Android App Links имеют значимое преимущество: поскольку они используют HTTP URL-адреса, которые ссылаются на домен вашего веб-сайта, никакое другое приложение не может использовать ваши ссылки. Одним из требований для Android App Links является подтверждение права собственности на ваш домен. В связи с этим пользователи, у которых не установлено приложение, просто переходят на ваш веб-сайт вместо приложения — никаких 404, никаких ошибок. Далее мы поговорим о процессе подтверждения права собственности на домен и посмотрим, возможно ли что-то сломать.

Верификация домена для App Links

На самом деле всё просто!

Для того чтобы приложение могло стать обработчиком по умолчанию для ссылок с вашего URL, необходимо подтвердить право собственности на домен. Это делается через Play Console и включает добавление специального файла assetlinks.json на ваш сервер.

Для этого заходим в свой аккаунт Play Console (это место, где вы публикуете свои приложения). Затем переходим в раздел "Ссылки на контент":

Далее нажимаем кнопку "Добавить домен":

Здесь у нас появится всплывающее окно, в котором нужно ввести домен, который вы хотите верифицировать:

После ввода домена у вас сгенерируется .json файл, который нужно будет добавить по пути, указанному ниже:

Переходим на свой сервер (мы будем использовать тестовый сервер) и добавляем наш сгенерированный файлик (в нашем случае просто обновляем):

Возвращаемся в Play Console и нажимаем на кнопку "Установить связь с сайтом":

Если вы всё проделали верно, то увидите внизу надпись:

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

Как сломать App Links?

Сломать App Link также очень просто. Достаточно внести изменения в файлик assetlinks.json (изменения, которые противоречат сгенерированному файлу) и ваш App Link превращается в Web Link со всеми вытекающими последствиями в виде диалогового окна "устранения неоднозначности".

Как проверить валидность App Links?

Поскольку апплинки можно легко сломать, нужно найти способ верификации, при котором App Link работает и домен верифицирован без нудного захода в Play Console. И такой способ есть!

Заходим в консоль на устройство, на котором установлено наше приложение, и вводим команду:

adb shell pm get-app-links com.swordfishsecurity.securenotes

Она выведет следующую информацию:

com.swordfishsecurity.securenotes:

    ID: 96a1f202-ac04-4dc1-95ad-2cb7d7b4057d

    Signatures: [33:90:0E:20:34:49:45:D2:0A:88:0D:36:D5:C1:75:70:50:BA:5C:66:44:03:79:05:E7:DF:5A:6B:7B:E2:17:BD]

    Domain verification state:

      devgymprojectdev.com: 1024

В Domain verification state могут отобразиться разные статусы. Но если мы не видим статус verified, значит что-то пошло не так и домен не верифицирован. В примере выше мы просто сломали файлик, поэтому у нас не отобразился статус verified. В нашем конкретном примере *1024 - это ошибка.

Атаки и уязвимости

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

И тут должно было быть детальное описание всех атак и уязвимостей, которые возникают при ошибках в выставлении App Links. Но, к сожалению, этого не будет — не все исследования заканчиваются успешным открытием нового направления атаки. Единственное, чего нам удалось добиться — это превращение App Links в обычные Web Links, которое влечет за собой появление диалога "устранения неоднозначности". Какие-то более существенные бреши и проблемы найти не удалось. Зато мы точно для себя разобрались, как ведет себя система в случае нескольких зарегистрированных Web Links или Deep Links и как избежать диалогового окна, чтобы не вводить пользователя в заблуждение.

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

Заключение

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

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

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

Спасибо!

Ссылки

  1. Android App-links

  2. Verify android applinks

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


  1. czuryk
    20.11.2024 14:47

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

    Выполнял секюрити ревью мобильного приложение заказчика, и обратил внимание что в трафике прилетает OAuth2.0 Auth Code в диплинке, написал свой POC, подписавшись на схему в диплинке, получилось перехватить запрос приложения, и если выбрать мой POC то удавалось стянуть AuthCode. Но сам код все же бесполезен, без CodeVerifier так как используется PCKE, однако я решил подумать дальше, что тут можно сделать.

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

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

    Со всем согласен с написанным, хотел добавить один момент, не упомянутый в статье, для митигации этой и подобных проблем:

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

    Надеюсь мой комментарий немного дополнит твою прекрасную заметку.


    1. Mr_R1p Автор
      20.11.2024 14:47

      Спасибо огромное!

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


  1. a_artikov
    20.11.2024 14:47

    Спасибо за статью.

    Единственное, чего нам удалось добиться — это превращение App Links в обычные Web Links, которое влечет за собой появление диалога "устранения неоднозначности".

    Можете, пожалуйста, раскрыть этот момент? Злоумышленник сможет сделать это, не имея доступа к нашему беку?


    1. Mr_R1p Автор
      20.11.2024 14:47

      Нет, к сожалению (или счастью), этого мы добились, когда пытались по разному модифицировать assetlinks.json.

      Пытались то домен другой подсунуть, то редирект сделать, то поле убрать/добавить и т.д. Но неизменно добивались только того, что App Links ломались, превращаясь в обычные WebLinks.