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

Don’t let attackers exploit your app via Intent...

Don’t let attackers exploit your app via Intents // Android Makers by droidcon 2025

My presentation about Intent-based exploits of Android apps and their mitigations as presented at Android Makers by droidcon on April 11th, 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

Safeguarding user security on Android​:
https://youtu.be/RccJYep2v5I

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

Avatar for Balázs Gerlei

Balázs Gerlei

April 11, 2025
Tweet

More Decks by Balázs Gerlei

Other Decks in Programming

Transcript

  1. Intents and Intent Filters • Primary IPC mechanism on Android

    • Three fundamental use case: • Start an Activity (potentially for a Result) • Start a Service • Deliver a Broadcast • Two main types: • Explicit: the specific target component is defined • Implicit: no target is defined, only the action • Intent filters specify which Intents a component handles @balazsgerlei, balazsgerlei.com
  2. PendingIntents • 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
  3. Intent DoS • A malicious app can send a malformed

    Intent to crash another app • The easiest is to not provide an Action (set it to null) • The most effective is to put a custom Serializable extra • It won’ be known by the recipient so it would crash if it tries to deserialize it • Simple form of Denial of Service @balazsgerlei, balazsgerlei.com
  4. Intent Fuzzer App • An app to try send malformed

    Intents to other apps • https://github.com/balazsgerlei/IntentFuzzer @balazsgerlei, balazsgerlei.com
  5. Sanitizing Intents – Intent DoS mitigation • Check for unexpected

    actions, extras, etc. • Handle Exceptions during parsing • Ignore malformed Intents • Clear malformed Bundles • The app may crash between Android 9 (API 28) and 11 (API 31) when encountering serializable extras with unknown classes • 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
  6. 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 • The attacker can Elevate its privileges • Most commonly it embeds its things in the extras field @balazsgerlei, balazsgerlei.com
  7. Intent redirection – mitigations • Sanitize Intents • Carefully implement

    parsing logic • Don’t launch Intents that are embedded into Intents from outside • Use (immutable) PendingIntents @balazsgerlei, balazsgerlei.com
  8. Explicit Intents partially matching Intent Filters • When the target

    component is specified (thus making the Intent explicit) the Intent gets delivered, even if the Action does not match • If after receiving the Intent, the logic is shared between a public and a private receiver, • Triggering a private Action through a public Receiver @balazsgerlei, balazsgerlei.com
  9. 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
  10. 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
  11. 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
  12. Explicit Intents partially matching Intent Filters – mitigations • Will

    be fully mitigated in Android 16 (API 36) • You should fully separate logic between different receivers, don’t rely solely on IntentFilters • You can also use IntentSanitizer here @balazsgerlei, balazsgerlei.com
  13. Intent Hijacking • A malicious app can hijack an Intent

    • If the sender does not specify a fully-qualified component class name or package when sending it • An attacker can access sensitive data • Or perform arbitrary actions (e.g., launch components) • Or modify a mutable PendingIntent • Since Android 12 (API 31) the mutability needs to be explicitly set @balazsgerlei, balazsgerlei.com
  14. Intent Hijacking – mitigations • Specify the target component •

    Narrow down the target • Don’t put sensitive data into (Implicit) Intents • Always use immutable PendingIntents • 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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 • App impersonation, phishing credentials • The user may not even know they fell victim @balazsgerlei, balazsgerlei.com
  22. 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
  23. 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
  24. 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
  25. 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
  26. StrandHogg 1 • The two 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 • 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
  27. 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
  28. StrandHogg 2 • More serious than the first • Fully

    dynamic, implemented in code • Multiple victims can be targeted • The code, or the list of targets can be loaded dynamically • The attacker may even check which app is installed from the intended targets • It can be 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
  29. StrandHogg 2 • The attacker app needs to be running

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

    in the background • But it doesn’t need to be launched first Victim Activity Victim Task Malicious Activity @balazsgerlei, balazsgerlei.com
  31. 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
  32. 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
  33. 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
  34. 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
  35. Safer Intents • Intents must have non-null Actions • Explicit

    Intents must match target component’s Intent Filters @balazsgerlei, balazsgerlei.com
  36. Safer Intents • It’s 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
  37. 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
  38. 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, this 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
  39. Main Takeaways • Sanitize Intents • 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
  40. Merci! Thank you! • speakerdeck.com/balazsgerlei • Android Security Evolution •

    github.com/balazsgerlei/AndroidSecurityEvolution • Safeguarding user security on Android • youtu.be/RccJYep2v5I • 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 @balazsgerlei, balazsgerlei.com