Уязвимая навигация в Android Jetpack navigation
Описание
В Android для навигации между фрагментами можно использовать NavController
, которому передается ресурс с графом навигации (/res/navigation/some_file.xml). Проблема в том, что если в теге <navigation>
используется атрибут android:id
, то к указанным фрагментам возможен доступ через диплинки. Таким образом стороннее приложение может потенциально открыть фрагмент приложения, указанный в файле (-ах) навигации. Пример атаки хорошо описан в этой статье.
Проблема
В Android можно указать навигацию между экранами, реализованную с помощью фрагментов следующим образом:
-
Создается файл(ы) с навигацией.
-
Создаются классы фрагментов.
-
Устанавливается «корневой фрагмент» —
NavHostFragmet
, объектNavController
которого используется далее для управления навигацией.
Файл навигации может выглядеть следующим образом:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/mobile_navigation"
app:startDestination="@+id/navigation_home">
<fragment
android:id="@+id/navigation_home"
android:name="ru.ptsecurity.navigation_example.ui.home.HomeFragment"
android:label="@string/title_home"
tools:layout="@layout/fragment_home">
<action android:id="@+id/toFirst"
app:destination="@+id/navigation_first">
</action>
</fragment>
<fragment
android:id="@+id/navigation_private"
android:name="ru.ptsecurity.navigation_example.ui.private.PrivateFragment"
android:label="@string/title_private"
tools:layout="@layout/fragment_private" />
<fragment
android:id="@+id/navigation_first"
android:name="ru.ptsecurity.navigation_example.ui.stack.FirstFragment"
android:label="@string/title_stack"
tools:layout="@layout/fragment_first">
<action android:id="@+id/toSecond"
app:destination="@+id/navigation_second">
</action>
<argument
android:name="textFirst"
app:argType="string"
android:defaultValue=""/>
</fragment>
// ....
Следует понимать, что все атрибуты id
имееют числовое значение в файле R.java.
Помимо создания флоу переходов между экранами, после имплементации такой навигации неочевидным образом появляется возможность получить доступ к нужному экрану (фрагменту) с помощью диплинков (см. документацию). По сути, чтобы запустить нужный экран целевого приложения извне, необходимо знать два числовых параметра: android:id
нужного тега <navigation>
и android:id
конкретного фрагмента, который предполагается запустить.
Например, если navigation android:id = 0xA
, а fragment android:id = 0xB
, то стороннее приложение может сформировать и послать такой Intent
:
am start -n com.example.app/.HostActivityClassName --eia "android-support-nav:controller:deepLinkIds" 0xA,0xB
При реализации такой атаки следует учитывать, что разные сборки приложения имеют разные значения для одних и тех же id
.
Рекомендации
Одним из способов защиты является неиспользование атрибута android:id
в теге navigation
- это не повлияет на навигацию, но диплинки работать перестанут. Однако, в некоторых случаях такой способ не подойдет. Кроме того, лучше всегда явно проверять авторизацию пользователя при доступе к очередному экрану. То есть, проверка возможности доступа должна осуществляться не в навигации, а в бизнес-логике приложения. В случае если пользователь не авторизован (не вводил пин-код или время сессии истекло), то в переходе на запрашиваемый экран ему должно быть отказано.