Deep Link and Intent Hijacking in Android Applications
Attackers can chain deep-link flaws to hijack accounts and reach private app components.

Deep link and intent hijacking in Android applications is a set of failures that can chain together, turning an ordinary link into account hijacking, access to private components, or a fake app screen. Finding and fixing these flaws means following attacker-controlled input through the whole chain, beyond checks at each entry point on its own.
How Android routes deep links and why the routing layer is an attack surface
Android declares deep-link routes through <intent-filter> elements in an app’s manifest. The PackageManager resolves incoming intents by matching them against registered filters across every installed app on the device. Routing is therefore a shared system. A matching filter identifies a possible destination. It does not prove who sent the request or whether the requested action is safe.
That gap makes routing an attack surface. Intent filters were not designed as a security control, so treating a successful match as permission to proceed gives outside input more trust than it deserves. Selectors show how routing and execution can diverge. An intent:// URL can use a selector that matches VIEW while naming com.victim/.InternalActivity as the component and SEND as the action ultimately delivered. Chrome blocks this path, but most WebView implementations and Firefox do not, so the software handling the link changes what can reach the app.
Custom URI scheme hijacking and why unverified schemes make OAuth callbacks a theft surface
Android does not verify who owns a custom URI scheme. Any installed app can register myapp://, making custom scheme hijacking the most common deep-link exploit pattern in the notes. In an OAuth flow, that ambiguity can become account takeover. A callback such as myapp://oauth?code=xyz may reach a malicious app registered for the same scheme, which extracts the authorization code and, if PKCE is absent, redeems it for an access token.
CVE-2026-26123 illustrates the problem in Microsoft Authenticator, which was patched in March 2026. Authenticator emitted an ms-msa:// deep link during QR-code account onboarding but did not claim that scheme through its own intent filter. A sibling app could register the scheme and receive the onboarding payload if the user selected it as the handler. That user choice was an exploitation requirement. The case demonstrates payload interception, rather than the OAuth redemption chain described above.
Oversecured explains that PKCE, or Proof Key for Code Exchange, reduces OAuth code theft risk by requiring a verifier before redemption. The intercepting app cannot redeem the code without that secret, but the OAuth client must implement PKCE, and many production apps omit it. The exposure also extends beyond OAuth callbacks. Password-reset tokens, magic login links, and other bearer values remain interceptable when an unverified scheme is the only claim about their destination.
Open redirect through deep link parameters and how weak host checks fail
An app can receive a link through its intended route and still send a WebView to an attacker’s site by accepting a URL from ?redirect=, ?next=, or ?url= without adequate validation. The trust boundary has moved into the parameter.
A startsWith check treats a URL as a prefix, while contains("trusted.com") only establishes that a string appears somewhere in it. Neither is a reliable host check. Oversecured found this in a major Asian super-app in production, where three different payload variants could get past a startsWith check in its routing library. The weakness was in how the destination was accepted.
Even a correctly matched allowlist can become unsafe when an approved domain expires and an attacker re-registers it. The host check then passes for exactly the intended name, but control of that name has changed. Static analysis cannot detect that ownership change. Finding that change takes runtime testing and checks on domain registration over time, so trust in a destination needs ongoing checks even when the code stays the same.
WebView as the second-stage amplifier when a redirect lands inside an authenticated session
A redirect into an app’s WebView can do more damage than an ordinary web redirect because the destination sits inside the authenticated app’s context. Depending on the WebView’s configuration, that environment can include session cookies, local storage, native JavaScript bridges, and local filesystem access. The page may reach more than a browser surface.
An attached interface created with addJavascriptInterface() is a particularly dangerous bridge. JavaScript in the loaded frame can call exposed native Android methods, so a redirect could lead to code execution inside the app’s sandbox. CVE-2022-28799 shows the account-level consequence in TikTok Android. A crafted, unvalidated deep link forced its WebView to load an arbitrary website, which could then reach an attached JavaScript interface. Microsoft showed token theft and profile modification, which backed the documented one-click account hijacking outcome but did not prove complete credential compromise.
The WebView can also become an entry point for another routing step. If the app handles an intent:// URL by calling Intent.parseUri() and passing the result to startActivity(), an attacker-controlled page can trigger that behavior. The page could have come from somewhere other than a deep link. Oversecured calls this “limited intent redirection,” because the app’s own launch path can reach internal non-exported activities while restricting some intent flags.
Intent redirection through an exported activity to private components
Direct intent redirection removes the need for the WebView step. An exported activity receives an external request, extracts a nested Intent from its extras, and passes it to startActivity() without checking its source or destination. The exported activity becomes a proxy into the app. The vulnerable app can launch a non-exported target activity on the attacker’s behalf.
This is why an exported, BROWSABLE entry point with no parameter validation is dangerous, even when its purpose is simply to make deep links work. The external request chooses what happens next inside the app. A nested intent passed through extras can include arbitrary flags and data, so it gives the attacker more ways to reach private components than the limited WebView variant does.
Microsoft’s Defender Security Research Team documented this supply-chain failure in EngageSDK. Its exported MTCommonActivity processed incoming intents and re-dispatched them with URI_ALLOW_UNSAFE, permitting persistable, read, and write URI permission flags. The team found that more than 30 million wallet app installations could be at risk of leaking private data across the Android sandbox boundary. The vulnerable code belonged to the SDK. EngageLab fixed it in version 5.2.1, released November 3, 2025, by making the activity non-exported.
PendingIntent provenance confusion and why creator identity does not equal sender identity
A PendingIntent identifies its creator. It also executes with the creator app’s identity when sent. Those are different facts, and confusing them can let an attacker impersonate a legitimate partner across an SDK’s partner ecosystem. Ramanpreet Singh Khinda’s research demonstrates why PendingIntent.getCreatorPackage() is unsuitable as proof of who is calling.
The attack needs notification access, a permission the user can grant. An attacker app with that access can obtain a PendingIntent from a legitimate partner app’s notification and present it to a platform SDK as proof of identity. If the SDK accepts the creator package as the caller’s identity, the attacker inherits the partner’s privileges. Both mutable and immutable PendingIntents can support this attack. Immutability protects the token’s contents.
Through fillIn(), the holder of a mutable PendingIntent can populate fields that the base intent left empty without changing whose privileges it uses. Analytics, push, and advertising SDKs often require mutable PendingIntents so they can inject extras when the operation fires. That dependency does not shift responsibility away from the integrating app during security review. The identity check and the mutable fields need separate scrutiny because fixing one does not resolve the other.
Task hijacking via taskAffinity manipulation and what StrandHogg established about UI spoofing
Task hijacking targets what the user sees. Android groups activities into task stacks using taskAffinity. An attacker app that claims the victim’s affinity and declares launchMode="singleTask" can insert itself into the victim’s task. The next time the user opens the legitimate app from the launcher, the attacker’s interface can appear instead. The familiar launch action lends credibility to the wrong screen.
StrandHogg established that this attack did not require root access, a native exploit, or a kernel vulnerability. Malicious dropper apps that installed its payloads were already available on the Play Store, so installing only from an official source did not prevent this route. An attacker’s screen can solicit credentials or biometric confirmations that the user believes belong to the legitimate app, without sending the user to a separate phishing page.
This is distinct from stealing a URI parameter. The attacker is manipulating the relationship between an app launch and the activity shown afterward. A review focused only on incoming data would miss that relationship. Task behavior therefore belongs in the same assessment as deep-link handling, even though its immediate effect is interface substitution.
How these patterns chain together in real exploits and why single-pattern thinking misses the actual risk
The TikTok and EngageSDK cases show how much risk a single vulnerability label can miss. TikTok’s chain ran from an unvalidated deep link through a WebView open redirect to a JavaScript bridge. The SDK chain ran from an exported activity through intent redirection with unsafe URI flags to private data across the sandbox boundary. Looking only at the original link or exported component would leave the eventual impact unexplained.
These chains also show why a successful fix can be narrower than it appears. Blocking any required step breaks that exploit path, but it does not necessarily remove the other unsafe settings. PKCE can prevent redemption of an intercepted OAuth code without making the custom scheme exclusive. Making an entry activity non-exported can close an external route without demonstrating that every internal destination validates its inputs.
WebViews often join those ingredients because they connect incoming links, web content, and intent parsing. Some failures occur only when those parts run together. An expired allowlisted domain can pass a code-level check, while the HierarchicalUri reflection attack can return legitimate.com from getHost() yet resolve to attacker.com when loaded. Selector behavior creates another mismatch between an accepted route and the resulting action. Static structure alone cannot establish what these paths actually do.
What effective testing for these vulnerabilities actually requires
Effective testing combines static inspection, dynamic runtime interception, and working exploit construction. Static analysis maps exported activities, permission checks, scheme declarations, verification settings, and mutable PendingIntents. Source and configuration review also exposes missing or wildcarded assetlinks.json paths. These are starting points for establishing exploitation. Whitebox access makes validation logic, intent-handling paths, and SDK integration points readable instead of leaving the tester to sample their behavior from outside.
Dynamic testing then establishes whether the intended protections work end to end. Android’s App Link verification checks the package’s identity and signing-certificate information against assetlinks.json over HTTPS. Liu et al.’s USENIX Security 2017 measurement found that few of the apps that shipped App Links passed full verification. A declaration is not a successful verification result. Runtime testing must also check who controls the domains and track what happens across WebView and intent boundaries.
To show what an attacker can do, testers should work through the chain from a suspicious setting to its effects. For OAuth, that means showing interception and redemption where possible. For a WebView path, it means loading the attacker-controlled destination and demonstrating access to the exposed bridge. For intent redirection, it means reaching the non-exported activity through the vulnerable entry point. Those demonstrations separate an exploitable path from a structural concern.
The assessment also needs to continue as the app changes. A new callback URI, an added route, or a newly integrated SDK can reopen a path that a point-in-time test found closed. Continuous scanning addresses those changes, while runtime testing establishes their consequences. The useful security finding is that untrusted input can cross a specific boundary and perform an action it should never control.


