Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Don’t let attackers exploit your Android app vi...

Don’t let attackers exploit your Android app via Intents // Hacktivity 2025

My presentation about Intent-based exploits of Android apps and their mitigations as presented at Hacktivity 2025 on October 3rd, 2025.

Intro
Intents are the starting points for every Android application. The platform is very much built on Activities, potentially from different apps interacting with each other to complete some tasks. This open nature can be an avenue for exploitation.

You have to consider Intents what they are: inputs. And inputs must be sanitized. With this mentality, you can protect against many attacks, but some can only be avoided with the right architecture and platform support. Google finally made strides in this area with Android 15’s safer Intents. At the same time, you need to understand the attack surface to defend your apps.

I will describe and demonstrate such issues:
- Privilege escalation via Intent redirection
- Denial-of-service via malformed Intents
- Leaking data via Intent parameter injection
- App impersonation via Task hijacking (StrandHogg)

At the end of the talk, you will have an understanding of mitigating and remediating many Intent-based Android vulnerabilities.

Links
Android Security Evolution:
https://github.com/balazsgerlei/AndroidSecurityEvolution

Intent Fuzzer fork that I maintain​:
https://github.com/balazsgerlei/IntentFuzzer

USENIX Security '15 - Towards Discovering and Understanding Task Hijacking in Android​:
https://youtu.be/IYGwXFIYdS8

HackTricks - Android Task Hijacking​:
https://book.hacktricks.wiki/en/mobile-pentesting/android-app-pentesting/android-task-hijacking.html

Application Security Cheat Sheet:
https://0xn3va.gitbook.io/cheat-sheets/android-application/intent-vulnerabilities

Avatar for Balázs Gerlei

Balázs Gerlei

October 03, 2025
Tweet

More Decks by Balázs Gerlei

Other Decks in Programming

Transcript

  1. Intent Use Cases 1. Start an Activity • May be

    for a Result 2. Start a Service 3. Send a Broadcast @balazsgerlei, balazsgerlei.com
  2. Intent Use Cases @balazsgerlei, balazsgerlei.com // Starting an Activity val

    activityIntent = Intent(this, OtherActivity::class.java).apply { putExtra("key", value) } startActivity(activityIntent) // Starting a Service val fileUrl = "file:///mnt/sdcard/foo.txt" val serviceIntent = Intent(this, MyService::class.java).apply { data = fileUrl.toUri() } startService(serviceIntent) // Sending a Broadcast val broadcastIntent = Intent("com.victim.messenger.IN_APP_MESSAGE").apply { putExtra("key", value) } sendBroadcats(broadcastIntent)
  3. Two Types of Intents Explicit Intent • Must specify the

    target component • Typically used to start a specific component (e.g. an Activity) Implicit Intent • Only need to specify the type of action to perform • E.g., start a gallery app to pick an image or share a file @balazsgerlei, balazsgerlei.com
  4. Intent Filter • Declared in Manifest • They describe what

    kind of actions a component (Activity, Broadcast Receiver, Content Provider) can handle • Marking the starting Activity (for launchers) is one of them • If declared, the exported flag must be set for the component • Can give a false sense of security @balazsgerlei, balazsgerlei.com
  5. Matching Intent and Intent Filter <intent-filter> <action android:name="android.intent.action.SEND"/> <category android:name="android.intent.category.DEFAULT"/>

    <data android:mimeType="text/plain"/> </intent-filter> @balazsgerlei, balazsgerlei.com val intent = Intent(Intent.ACTION_SEND).apply { addCategory(Intent.CATEGORY_DEFAULT) type = "text/plain" }
  6. Matching Intent and Intent Filter <intent-filter> <action android:name="android.intent.action.SEND"/> <category android:name="android.intent.category.DEFAULT"/>

    <data android:mimeType="text/plain"/> </intent-filter> @balazsgerlei, balazsgerlei.com val intent = Intent(Intent.ACTION_SEND).apply { addCategory(Intent.CATEGORY_DEFAULT) type = "text/plain" }
  7. Matching Intent and Intent Filter <intent-filter> <action android:name="android.intent.action.SEND"/> <category android:name="android.intent.category.DEFAULT"/>

    <data android:mimeType="text/plain"/> </intent-filter> @balazsgerlei, balazsgerlei.com val intent = Intent(Intent.ACTION_SEND).apply { addCategory(Intent.CATEGORY_DEFAULT) type = "text/plain" }
  8. Matching Intent and Intent Filter <intent-filter> <action android:name="android.intent.action.SEND"/> <category android:name="android.intent.category.DEFAULT"/>

    <data android:mimeType="text/plain"/> </intent-filter> @balazsgerlei, balazsgerlei.com val intent = Intent(Intent.ACTION_SEND).apply { addCategory(Intent.CATEGORY_DEFAULT) type = "text/plain" }
  9. Matching Intent and Intent Filter <intent-filter> <action android:name="android.intent.action.SEND"/> <category android:name="android.intent.category.DEFAULT"/>

    <data android:mimeType="text/plain"/> </intent-filter> @balazsgerlei, balazsgerlei.com val intent = Intent(Intent.ACTION_SEND).apply { addCategory(Intent.CATEGORY_DEFAULT) type = "text/plain" }
  10. PendingIntent • A PendingIntent is an Intent that is set-up

    to be launched at a later point • E.g., the Intent wrapped into a Notification • Allows apps to take actions on behalf of another app • Using that app's identity and permissions @balazsgerlei, balazsgerlei.com
  11. Intent DoS • A malicious app can send a malformed

    Intent to crash another app • The easiest is without an Action (set to null) • The most effective is with a custom Serializable extra • Unknown by the recipient • Simple form of Denial of Service @balazsgerlei, balazsgerlei.com
  12. Sanitizing Intents – Intent DoS Mitigation • Check for unexpected

    actions, extras, etc. when parsing • Handle Exceptions • Ignore malformed Intents • Clear malformed Bundles • Otherwise crash because of Serializable extra between Android 9 (API 28) and 11 (API 31) • Use IntentSanitizer from androidx.core IntentSanitizer.Builder() .allowAnyComponent() .allowAction("android.intent.action.MAIN") .allowCategory("android.intent.category.LAUNCHER") .allowFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .build() .sanitizeByThrowing(intent) @balazsgerlei, balazsgerlei.com
  13. Intent Redirection • An attacker fully or partially controls the

    content of an Intent that is used to launch a component in the context of a victim app • Elevation of Privilege • Most commonly an embedded Intent in the extras field @balazsgerlei, balazsgerlei.com
  14. Intent Redirection – Mitigations • Mitigated in Android 16 (API

    36) • Sanitize and Filter Intents • Carefully implement parsing logic • Don’t launch Intents that are embedded into Intents from outside • Don’t use a proxy Activity • Use (immutable) PendingIntents @balazsgerlei, balazsgerlei.com
  15. Intent Redirection via WebView • WebView is the default UI

    component to embed web content to Android apps • Can be part of the layout • Or initiated in code • Provides callbacks (and a JavaScript bridge) to interact with native code @balazsgerlei, balazsgerlei.com
  16. Intent Redirection via WebView • A faulty parsing logic in

    the shouldOverrideUrlLoading method of WebViewClient • Called when a URL is about to be loaded into the WebView • You may create an Intent via parsing the URL String • It could expose private components (e.g., Activities) via component and selector fields @balazsgerlei, balazsgerlei.com
  17. Intent Redirection via WebView @balazsgerlei, balazsgerlei.com override fun shouldOverrideUrlLoading(view: WebView?,

    url: String?): Boolean { startActivity(it) } return true } .also { Intent.parseUri(url, 0)
  18. Intent Redirection via WebView @balazsgerlei, balazsgerlei.com override fun shouldOverrideUrlLoading(view: WebView?,

    url: String?): Boolean { startActivity(it) } return true } .also { Intent.parseUri(url, 0)
  19. Intent Redirection via WebView @balazsgerlei, balazsgerlei.com override fun shouldOverrideUrlLoading(view: WebView?,

    url: String?): Boolean { startActivity(it) } return true } .also { Intent.parseUri(url, 0)
  20. Intent Redirection via WebView @balazsgerlei, balazsgerlei.com override fun shouldOverrideUrlLoading(view: WebView?,

    url: String?): Boolean { startActivity(it) } return true } .also { Intent.parseUri(url, 0)
  21. Intent Redirection via WebView @balazsgerlei, balazsgerlei.com override fun shouldOverrideUrlLoading(view: WebView?,

    url: String?): Boolean { startActivity(it) } return true } .also { Intent.parseUri(url, 0)
  22. Intent Redirection via WebView @balazsgerlei, balazsgerlei.com override fun shouldOverrideUrlLoading(view: WebView?,

    url: String?): Boolean { startActivity(it) } return true } .also { Intent(Intent.ACTION_VIEW, Uri.parse(url))
  23. Intent Redirection via WebView @balazsgerlei, balazsgerlei.com override fun shouldOverrideUrlLoading(view: WebView?,

    url: String?): Boolean { startActivity(it) } return true } .also { Intent(Intent.ACTION_VIEW, Uri.parse(url))
  24. Intent Redirection via WebView @balazsgerlei, balazsgerlei.com override fun shouldOverrideUrlLoading(view: WebView?,

    url: String?): Boolean { startActivity(it) } return true } .also { Intent(Intent.ACTION_VIEW, Uri.parse(url))
  25. Intent Redirection via WebView @balazsgerlei, balazsgerlei.com override fun shouldOverrideUrlLoading(view: WebView?,

    url: String?): Boolean { Intent.parseUri(url, 0) .apply { addCategory(Intent.CATEGORY_BROWSABLE) component = null selector = null } .also { startActivity(it) } return true }
  26. Intent Redirection via WebView @balazsgerlei, balazsgerlei.com override fun shouldOverrideUrlLoading(view: WebView?,

    url: String?): Boolean { Intent.parseUri(url, 0) .apply { addCategory(Intent.CATEGORY_BROWSABLE) component = null selector = null } .also { startActivity(it) } return true }
  27. Intent Redirection via WebView @balazsgerlei, balazsgerlei.com override fun shouldOverrideUrlLoading(view: WebView?,

    url: String?): Boolean { Intent.parseUri(url, 0) .apply { addCategory(Intent.CATEGORY_BROWSABLE) component = null selector = null } .also { startActivity(it) } return true }
  28. Intent Redirection via WebView @balazsgerlei, balazsgerlei.com override fun shouldOverrideUrlLoading(view: WebView?,

    url: String?): Boolean { Intent.parseUri(url, 0) .apply { addCategory(Intent.CATEGORY_BROWSABLE) component = null selector = null } .also { } return true } val activityInfo = it.resolveActivityInfo( packageManager, PackageManager.MATCH_DEFAULT_ONLY) if (activityInfo.exported) { } startActivity(it)
  29. Intent Redirection via WebView @balazsgerlei, balazsgerlei.com override fun shouldOverrideUrlLoading(view: WebView?,

    url: String?): Boolean { Intent.parseUri(url, 0) .apply { addCategory(Intent.CATEGORY_BROWSABLE) component = null selector = null } .also { } return true } val activityInfo = it.resolveActivityInfo( packageManager, PackageManager.MATCH_DEFAULT_ONLY) if (activityInfo.exported) { } startActivity(it)
  30. Intent Redirection via WebView - Mitigations • Think through the

    functionality you want to provide • Use the constructor of Intent • Explicitly set component and selector to null • Check exported status of the component before calling @balazsgerlei, balazsgerlei.com
  31. More WebView-related vulnerabilities • “Overcoming Unsecurities in WebViews” – droidcon

    London • youtu.be/fqaUJ08MQDo @balazsgerlei, balazsgerlei.com
  32. Explicit Intents partially matching Intent Filters @balazsgerlei, balazsgerlei.com ExternalReceiver exported="true"

    InternalReceiver exported="false" Implicit Intent action: PUBLIC_EXTERNAL_ACTION PUBLIC_EXTERNAL_ACTION PRIVATE_INTERNAL_ACTION
  33. Explicit Intents partially matching Intent Filters @balazsgerlei, balazsgerlei.com ExternalReceiver exported="true"

    InternalReceiver exported="false" Implicit Intent action: PUBLIC_EXTERNAL_ACTION PUBLIC_EXTERNAL_ACTION PRIVATE_INTERNAL_ACTION
  34. Explicit Intents partially matching Intent Filters @balazsgerlei, balazsgerlei.com ExternalReceiver exported="true"

    InternalReceiver exported="false" Implicit Intent action: PRIVATE_INTERNAL_ACTION PUBLIC_EXTERNAL_ACTION PRIVATE_INTERNAL_ACTION
  35. Explicit Intents partially matching Intent Filters @balazsgerlei, balazsgerlei.com ExternalReceiver exported="true"

    InternalReceiver exported="false" Implicit Intent action: PRIVATE_INTERNAL_ACTION PUBLIC_EXTERNAL_ACTION PRIVATE_INTERNAL_ACTION
  36. Explicit Intents partially matching Intent Filters @balazsgerlei, balazsgerlei.com ExternalReceiver exported="true"

    InternalReceiver exported="false" Implicit Intent action: PRIVATE_INTERNAL_ACTION PUBLIC_EXTERNAL_ACTION PRIVATE_INTERNAL_ACTION
  37. Explicit Intents partially matching Intent Filters @balazsgerlei, balazsgerlei.com ExternalReceiver exported="true"

    InternalReceiver exported="false" Explicit Intent action: PRIVATE_INTERNAL_ACTION component: ".ExternalReceiver" PUBLIC_EXTERNAL_ACTION PRIVATE_INTERNAL_ACTION
  38. Explicit Intents partially matching Intent Filters @balazsgerlei, balazsgerlei.com ExternalReceiver exported="true"

    InternalReceiver exported="false" Explicit Intent action: PRIVATE_INTERNAL_ACTION component: ".ExternalReceiver" PUBLIC_EXTERNAL_ACTION PRIVATE_INTERNAL_ACTION
  39. Explicit Intents partially matching Intent Filters @balazsgerlei, balazsgerlei.com ExternalReceiver exported="true"

    InternalReceiver exported="false" Explicit Intent action: PRIVATE_INTERNAL_ACTION component: ".ExternalReceiver" PUBLIC_EXTERNAL_ACTION PRIVATE_INTERNAL_ACTION
  40. Explicit Intents partially matching Intent Filters @balazsgerlei, balazsgerlei.com ExternalReceiver exported="true"

    InternalReceiver exported="false" Explicit Intent action: PRIVATE_INTERNAL_ACTION component: ".ExternalReceiver" PUBLIC_EXTERNAL_ACTION PRIVATE_INTERNAL_ACTION
  41. Explicit Intents partially matching Intent Filters • When the target

    component is specified (explicit Intent) the Intent gets delivered, even if the Action does not match • Sharing the parsing logic between a public and a private receiver • It can lead to triggering a private action through a public receiver @balazsgerlei, balazsgerlei.com
  42. Explicit Intents partially matching Intent Filters <receiver android:name=".ExternalReceiver" android:exported="true"> <intent-filter>

    <action android:name="com.example.victim.PUBLIC_EXTERNAL_ACTION" /> </intent-filter> </receiver> <receiver android:name=".InternalReceiver" android:exported="false"> <intent-filter> <action android:name="com.example.victim.PRIVATE_INTERNAL_ACTION" /> </intent-filter> </receiver> @balazsgerlei, balazsgerlei.com
  43. Explicit Intents partially matching Intent Filters <receiver android:name=".ExternalReceiver" android:exported="true"> <intent-filter>

    <action android:name="com.example.victim.PUBLIC_EXTERNAL_ACTION" /> </intent-filter> </receiver> <receiver android:name=".InternalReceiver" android:exported="false"> <intent-filter> <action android:name="com.example.victim.PRIVATE_INTERNAL_ACTION" /> </intent-filter> </receiver> @balazsgerlei, balazsgerlei.com
  44. Explicit Intents partially matching Intent Filters <receiver android:name=".ExternalReceiver" android:exported="true"> <intent-filter>

    <action android:name="com.example.victim.PUBLIC_EXTERNAL_ACTION" /> </intent-filter> </receiver> <receiver android:name=".InternalReceiver" android:exported="false"> <intent-filter> <action android:name="com.example.victim.PRIVATE_INTERNAL_ACTION" /> </intent-filter> </receiver> @balazsgerlei, balazsgerlei.com
  45. Explicit Intents partially matching Intent Filters class ExternalReceiver : BroadcastReceiver()

    { override fun onReceive(context: Context, intent: Intent) { CentralizedIntentHandler.handleIntent(context, intent) } } class InternalReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { CentralizedIntentHandler.handleIntent(context, intent) } } @balazsgerlei, balazsgerlei.com
  46. Explicit Intents partially matching Intent Filters class ExternalReceiver : BroadcastReceiver()

    { override fun onReceive(context: Context, intent: Intent) { CentralizedIntentHandler.handleIntent(context, intent) } } class InternalReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { CentralizedIntentHandler.handleIntent(context, intent) } } @balazsgerlei, balazsgerlei.com
  47. Explicit Intents partially matching Intent Filters class ExternalReceiver : BroadcastReceiver()

    { override fun onReceive(context: Context, intent: Intent) { CentralizedIntentHandler.handleIntent(context, intent) } } class InternalReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { CentralizedIntentHandler.handleIntent(context, intent) } } @balazsgerlei, balazsgerlei.com
  48. Explicit Intents partially matching Intent Filters class ExternalReceiver : BroadcastReceiver()

    { override fun onReceive(context: Context, intent: Intent) { CentralizedIntentHandler.handleIntent(context, intent) } } class InternalReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { CentralizedIntentHandler.handleIntent(context, intent) } } @balazsgerlei, balazsgerlei.com
  49. Explicit Intents partially matching Intent Filters object CentralizedIntentHandler { fun

    handleIntent(context: Context, intent: Intent) { when (intent.action!!) { PUBLIC_EXTERNAL_ACTION -> { Toast.makeText(context, "EXECUTING EXTERNAL ACTION...", Toast.LENGTH_LONG) .show() } PRIVATE_INTERNAL_ACTION -> { Toast.makeText(context, "EXECUTING INTERNAL ACTION...", Toast.LENGTH_LONG) .show() with(context) { Intent(this, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }.also { startActivity(it) } } } } } } @balazsgerlei, balazsgerlei.com
  50. Explicit Intents partially matching Intent Filters object CentralizedIntentHandler { fun

    handleIntent(context: Context, intent: Intent) { when (intent.action!!) { PUBLIC_EXTERNAL_ACTION -> { Toast.makeText(context, "EXECUTING EXTERNAL ACTION...", Toast.LENGTH_LONG) .show() } PRIVATE_INTERNAL_ACTION -> { Toast.makeText(context, "EXECUTING INTERNAL ACTION...", Toast.LENGTH_LONG) .show() with(context) { Intent(this, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }.also { startActivity(it) } } } } } } @balazsgerlei, balazsgerlei.com
  51. Explicit Intents partially matching Intent Filters object CentralizedIntentHandler { fun

    handleIntent(context: Context, intent: Intent) { when (intent.action!!) { PUBLIC_EXTERNAL_ACTION -> { Toast.makeText(context, "EXECUTING EXTERNAL ACTION...", Toast.LENGTH_LONG) .show() } PRIVATE_INTERNAL_ACTION -> { Toast.makeText(context, "EXECUTING INTERNAL ACTION...", Toast.LENGTH_LONG) .show() with(context) { Intent(this, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }.also { startActivity(it) } } } } } } @balazsgerlei, balazsgerlei.com 0
  52. Explicit Intents partially matching Intent Filters object CentralizedIntentHandler { fun

    handleIntent(context: Context, intent: Intent) { when (intent.action!!) { PUBLIC_EXTERNAL_ACTION -> { Toast.makeText(context, "EXECUTING EXTERNAL ACTION...", Toast.LENGTH_LONG) .show() } PRIVATE_INTERNAL_ACTION -> { Toast.makeText(context, "EXECUTING INTERNAL ACTION...", Toast.LENGTH_LONG) .show() with(context) { Intent(this, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }.also { startActivity(it) } } } } } } @balazsgerlei, balazsgerlei.com
  53. Explicit Intents partially matching Intent Filters object CentralizedIntentHandler { fun

    handleIntent(context: Context, intent: Intent) { when (intent.action!!) { PUBLIC_EXTERNAL_ACTION -> { Toast.makeText(context, "EXECUTING EXTERNAL ACTION...", Toast.LENGTH_LONG) .show() } PRIVATE_INTERNAL_ACTION -> { Toast.makeText(context, "EXECUTING INTERNAL ACTION...", Toast.LENGTH_LONG) .show() with(context) { Intent(this, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }.also { startActivity(it) } } } } } } @balazsgerlei, balazsgerlei.com
  54. Explicit Intents partially matching Intent Filters object CentralizedIntentHandler { fun

    handleIntent(context: Context, intent: Intent) { when (intent.action!!) { PUBLIC_EXTERNAL_ACTION -> { Toast.makeText(context, "EXECUTING EXTERNAL ACTION...", Toast.LENGTH_LONG) .show() } PRIVATE_INTERNAL_ACTION -> { Toast.makeText(context, "EXECUTING INTERNAL ACTION...", Toast.LENGTH_LONG) .show() with(context) { Intent(this, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK }.also { startActivity(it) } } } } } } @balazsgerlei, balazsgerlei.com
  55. Explicit Intents partially matching Intent Filters - Mitigations • Fully

    mitigated in Android 16 (API 36) • Fully separate logic between different receivers, don’t rely solely on IntentFilters • Can use IntentSanitizer here too @balazsgerlei, balazsgerlei.com
  56. Intent Hijacking • A malicious app can • Hijack an

    (implicit) Intent • If the sender does not specify a fully-qualified component class name or package • An attacker can • Access sensitive data • Perform arbitrary actions (e.g., launch components) • Modify a mutable PendingIntent • Mutability needs to be explicitly set since Android 12 (API 31) @balazsgerlei, balazsgerlei.com
  57. Intent Hijacking - Mitigations • Specify the target component (if

    possible) • Narrow down the target • Don’t put sensitive data into (Implicit) Intents • Make PendingIntents immutable • Use the Photo Picker component to pick media • From androidx.activity library • Current version still supports Android 5 (API 21) – older versions Android 4.4 (API 19) @balazsgerlei, balazsgerlei.com
  58. Tasks and the Back Stack • Tasks are a stack

    of Activities • The Back Stack is the history of Activities that the user can navigate back to Activity 1 Task 1 @balazsgerlei, balazsgerlei.com
  59. Tasks and the Back Stack • Tasks are a stack

    of Activities • The Back Stack is the history of Activities that the user can navigate back to Activity 1 Task 1 Activity 2 @balazsgerlei, balazsgerlei.com
  60. Tasks and the Back Stack • Tasks are a stack

    of Activities • The Back Stack is the history of Activities that the user can navigate back to Activity 1 Task 1 Activity 2 Activity 3 @balazsgerlei, balazsgerlei.com
  61. Tasks and the Back Stack • Tasks are a stack

    of Activities • The Back Stack is the history of Activities that the user can navigate back to Activity 1 Task 1 Activity 2 @balazsgerlei, balazsgerlei.com
  62. Tasks and the Back Stack • Tasks are a stack

    of Activities • The Back Stack is the history of Activities that the user can navigate back to Activity 1 Task 1 Activity 2 Task 2 Activity 1 @balazsgerlei, balazsgerlei.com
  63. Tasks and the Back Stack • Tasks are a stack

    of Activities • The Back Stack is the history of Activities that the user can navigate back to Activity 1 Task 1 Activity 2 Task 2 Activity 1 @balazsgerlei, balazsgerlei.com
  64. StrandHogg 1 • Static attack, the malicious app needs to

    target a specific application in it’s Manifest • Can be found via static analysis • Should not go through Play Review • Can be used for • Privilege Escalation by asking for permissions • Spoofing (app impersonation), phishing credentials • The user may not even know they fell victim @balazsgerlei, balazsgerlei.com
  65. StrandHogg 1 • The user need to launch the malicious

    app first • Then next time they try to launch the victim, it will be brought back to the foreground instead Malicious Activity Hijacker Task @balazsgerlei, balazsgerlei.com
  66. StrandHogg 1 • The user need to launch the malicious

    app first • Then next time they try to launch the victim, it will be brought back to the foreground instead Malicious Activity Hijacker Task Victim Task @balazsgerlei, balazsgerlei.com
  67. StrandHogg 1 • The user need to launch the malicious

    app first • Then next time they try to launch the victim, it will be brought back to the foreground instead Malicious Activity Hijacker Task Victim Task Same Task Affinity @balazsgerlei, balazsgerlei.com
  68. StrandHogg 1 • The user need to launch the malicious

    app first • Then next time they try to launch the victim, it will be brought back to the foreground instead Malicious Activity Hijacker Task Victim Task Same Task Affinity @balazsgerlei, balazsgerlei.com
  69. StrandHogg 1 • Key configurations in Manifest: • android:taskAffinity –

    Activities with the same affinity conceptually belong to the same task. The affinity of a task is determined by the affinity of its root Activity. • Can be set to anything • If not set, it’s the application ID by default • android:allowTaskReparenting – whether the activity can move from the task that started it to the task it has an affinity for when that task is next brought to the front • android:excludeFromRecents – whether the task initiated by this Activity is excluded from the Recents screen @balazsgerlei, balazsgerlei.com
  70. StrandHogg 1 – Mitigations • Fully fixed in Android 11

    (API 30) • If your app can run on older Android versions, it’s affected • Specify an empty taskAffinity for your Activities • Much easier with a single Activity • Specify a singleInstance launch mode • Can result in broken user experience @balazsgerlei, balazsgerlei.com
  71. StrandHogg 2 • More serious than the first variant •

    Fully dynamic, implemented in code • Multiple victims can be targeted • Can load code, or the list of targets dynamically • The attacker may check which target apps are installed • Used for the same goals (permissions, credentials) • Easy to implement, hard to detect and mitigate • The user may not even know they fell victim @balazsgerlei, balazsgerlei.com
  72. StrandHogg 2 • The attacker app needs to be running

    in the background • It doesn’t need to be launched first Victim Activity Task 1 @balazsgerlei, balazsgerlei.com
  73. StrandHogg 2 • The attacker app needs to be running

    in the background • It doesn’t need to be launched first Victim Activity Task 1 Malicious Activity @balazsgerlei, balazsgerlei.com
  74. StrandHogg 2 • The attacker app needs to be running

    in the background • It doesn’t need to be launched first Victim Activity Task 1 Malicious Activity Task 2 Distraction Activity @balazsgerlei, balazsgerlei.com
  75. StrandHogg 2 • The attacker app needs to be running

    in the background • It doesn’t need to be launched first Victim Activity Task 1 Malicious Activity @balazsgerlei, balazsgerlei.com
  76. StrandHogg 2 • The key is launching three Intents: •

    1st launches the target Activity • It needs to have Intent.FLAG_ACTIVITY_NEW_TASK • 2nd is the malicious one, launching the attacker Activity • 3rd is a distraction, belonging to the attacker app that can provide benign functionality • It also needs to have Intent.FLAG_ACTIVITY_NEW_TASK • Need to be pass these to a single startActivities() call @balazsgerlei, balazsgerlei.com
  77. StrandHogg 2 – Mitigations • Fully fixed in Android 10

    (API 29) • The fix backported to Android 8, 8.1 and 9 (API 26, 27 and 28) via the May 2020 security update • Specify a singleInstance launch mode • Can result in broken user experience • Keep track of the number of Activities and abort if it differs • Hard to implement and prone to both false positives and missing the attack @balazsgerlei, balazsgerlei.com
  78. Background Activity Launch restrictions • Since Android 10 (API 29)

    apps can start activities when one or more of ~13 conditions are met, e.g.: • The app has a visible window, such as an activity in the foreground. • The app has an activity in the back stack of the foreground task. • The app has an activity in the back stack of an existing task on the Recents screen. • The app has an activity that started very recently. @balazsgerlei, balazsgerlei.com
  79. Android 15 (API 35) Android 16 (API 36) • Secured

    BAL • Don’t bring the Task to the foreground when launching Activities from the background • PendingIntent creators are blocked from background activity launches by default • Safer Intents • Not enforced (to ease adoption) • Prevent launching activities from other apps into your own task • Default security against general Intent redirection attacks • Apps will be able to enforce “Safer Intents” restrictions! @balazsgerlei, balazsgerlei.com
  80. Safer Intents 1. Intents must have non-null Actions 2. Explicit

    Intents must match target component’s Intent Filters @balazsgerlei, balazsgerlei.com
  81. Safer Intents • Not enforced in Android 15 (API 35)

    but can be detected via StrictMode (e.g., print violations to LogCat) • Apps targeting Android 16 (API 36) should be able to apply these restrictions via the new intentMatchingFlags in Manifest • none – disable all special matching rules • enforceIntentFilter – Explicit intents should match the target component's intent filter and Intents without an action should not match any intent filter • allowNullAction – Should be used together with enforceIntentFilter, and if so it relaxes the matching rules to allow intents without an action to match @balazsgerlei, balazsgerlei.com
  82. Prevent launching activities from other apps into your own task

    • android:allowCrossUidActivitySwitchFromBelow • Disallowed by default, can allow it, in a per-Activity basis • But both apps need to target the new Android version, the one in the foreground and the one in the background that tries to launch its Activity on top @balazsgerlei, balazsgerlei.com
  83. Prevent launching activities from other apps into your own task

    • android:allowCrossUidActivitySwitchFromBelow • Disallowed by default, can allow it, in a per-Activity basis • But both apps need to target the new Android version, the one in the foreground and the one in the background that tries to launch its Activity on top • Unfortunately, it has been pulled from Android 15 at the last minute (it’s not in Android 16 yet either) • Due to the many bugs reported @balazsgerlei, balazsgerlei.com
  84. Takeaways • Sanitize Intents • Use allow list, not block

    list • Do Threat Modelling • Simplify Activity usage (use a single Activity if possible) • Restrict what components can join your Task • Set an empty taskAffinity instead of relying on the default • Set a restrictive launch mode for your Activity (preferably singleInstance) @balazsgerlei, balazsgerlei.com
  85. Köszönöm! Thank you! • speakerdeck.com/balazsgerlei • Android Security Evolution •

    github.com/balazsgerlei/AndroidSecurityEvolution • Intent Fuzzer fork that I maintain • github.com/balazsgerlei/IntentFuzzer • USENIX Security '15 - Towards Discovering and Understanding Task Hijacking in Android • youtu.be/IYGwXFIYdS8 • HackTricks - Android Task Hijacking • book.hacktricks.wiki/en/mobile-pentesting/android-app-pentesting/android-task- hijacking.html • Application Security Cheat Sheet • 0xn3va.gitbook.io/cheat-sheets/android-application/intent-vulnerabilities @balazsgerlei, balazsgerlei.com