
Android deep link vulnerabilities: how intent filters lead to account takeover | Oversecured Blog
Oversecured's article on Android deep-link vulnerabilities covers five patterns: custom scheme hijacking, open redirect into WebView, limited and full intent redirection, and domain takeover via an expired whitelist entry. It shows bypass payloads, intent selector abuse, and the HierarchicalUri reflection bypass. It also compares what SAST and DAST can find and gives a checklist for pentesters.
What it is
Oversecured's write-up of five Android deep-link vulnerability patterns seen in production apps, with a pentest checklist. The vendor frames it around its own SAST/DAST scanner, but the technical detail stands on its own. Its central claim is that the damaging findings are chains of three or four steps, not single flaws.
How it works
Intent filters are not an access control. An intent:// URL with a selector can target a component directly and override filter matching:
intent://open#Intent;scheme=myapp;component=com.victim/.InternalActivity;action=android.intent.action.SEND;SEL;action=android.intent.action.VIEW;endPassed to startActivity(), this launches InternalActivity with a SEND action even though the filter matched VIEW. Chrome blocks selectors. Most WebView implementations and Firefox do not.
The five patterns:
- Custom scheme hijacking. Any app can claim
myapp://, and a malicious app can register a higher priority (up to 999). The main risk is an OAuth callback (myapp://oauth?code=xyz) or any token delivered by deep link, such as password reset or magic login links. - Open redirect into WebView.
startsWith("https://trusted.com")andcontains("trusted.com")checks fail againsthttps://trusted.com@attacker.com/andhttps://trusted.com.attacker.com/steal. The page says a major Asian super-app's routing library had astartsWithcheck with three bypass variants. - Limited intent redirection. A user taps an
intent://link inside a WebView, the app callsIntent.parseUri()andstartActivity(), and a non-exported internal Activity launches. A deep link is not needed as the entry point. Certain flags and data types are blocked. - Full intent redirection. An exported activity takes a nested Intent from extras and passes it to
startActivity(). The attacker controls flags, includingFLAG_GRANT_READ_URI_PERMISSION, which reaches non-exported Content Providers and enables file exfiltration. - Expired whitelist domain. The Android whitelist check is correct, but one listed domain has lapsed. The attacker registers it, the app loads it and sends authorization headers, and the attacker captures a valid token.
flowchart TD
A[Exported + BROWSABLE + no validation] --> B[Custom scheme claimed]
A --> C[URL param into WebView]
A --> D[Nested Intent in extras]
C --> E[intent:// link in WebView]
E --> F[Intent.parseUri to startActivity]
D --> G[startActivity with attacker flags]
F --> H[Internal Activity reached]
G --> I[Provider read / file exfil]
B --> J[OAuth code or token intercepted]Preconditions / Affected
- The most common risky setup is
android:exported="true"plus the BROWSABLE category plus no parameter validation. - Scheme hijacking on OAuth needs a flow without PKCE. Android 12+ sends unverified links to the browser by default, but the page says the risk then shifts to OAuth flows. Account takeover is "possible but not guaranteed".
- The HierarchicalUri bypass lets a crafted
android.net.Urisubclass, built by reflection, returnlegitimate.comfromgetHost()whilewebView.loadUrl()goes toattacker.com. It needs a second app on the device that passes the Uri object. It affected Android before 12. From API 28 internal interfaces are restricted, though RestrictionBypass can circumvent that. - App Links remove scheme hijacking but can fail through a misconfigured
assetlinks.json, wrong SHA-256 fingerprints, or silent verification failure that falls back to the browser.
What to do
Audit for these:
- Every exported Activity with BROWSABLE.
- URL parameters flowing into WebView. Test for whitelist bypasses, not just whether a check exists.
getParcelableExtra()on exported components, where the Parcelable is an Intent passed straight tostartActivity().Intent.parseUri()calls downstream of WebView URL loading.- OAuth callbacks: use verified App Links, never custom schemes, and check for PKCE.
- Domains in any whitelist: monitor their registration status, since code review cannot show expiry.
The page maps these issues to OWASP Mobile Top 10 M3 and M8, and points to MASTG "Testing Deep Links".
Caveats
- SAST flags
contains/startsWithhost checks and traces data flow to sinks. It cannot see expired whitelist domains, server-side redirect token leaks, or which app handles a scheme at runtime. - Backend-dependent issues are hard to detect automatically, including by DAST. The vendor says it is still working on this.
- The page gives no CVE ids, and its real-world examples are unnamed.
Organisations named here
- App Links
- custom scheme hijacking
- deep link hijacking
- HierarchicalUri bypass
- intent selectors
- OWASP MASTG
- OWASP Mobile Top 10
- PKCE
Technologies named here
- SAST
- DAST
- Firefox
- Android
- OAuth
- Chrome
- Oversecured
- A major Asian super-app (routing library with bypassable startsWith check; intent:// link chain in WebView)
- Android 12+ (unverified links default to the browser)
- Android versions before Android 12 (HierarchicalUri bypass)
- Android WebView
- assetlinks.json
- Oversecured scanner
- OWASP Mobile Top 10
- PKCE
- RestrictionBypass
Techniques named here
- Open redirect
- Intent Redirection
- App Links verification bypass
- Custom URI scheme hijacking
- Domain takeover via expired whitelist entry
- Full intent redirection
- HierarchicalUri bypass
- Intent selector bypass of intent filters
- Limited intent redirection via WebView
- Open redirect via deep links
- Taint analysis with runtime confirmation
Related in Mobile Security
thomasbuilds/Spectre
