← Back to all reports

Intent URI Bypass Loads Attacker Page in Exchange WebView, JS Bridge Returns Auth Token For One-Click Account Takeover

Reported Mar 5, 2026
Severity Critical
Platform Android
Vulnerability Class Deep Link Validation Bypass (CWE-939)
Target Type Centralized Cryptocurrency Exchange
Impact One-tap account takeover, full spot, futures, DEX, and OTC API access

The Risk

Any logged-in user of the exchange's app could have their account taken over by tapping a single link in any browser, email, or chat. The link silently opens the app, loads an attacker page inside the in-app browser, and that page reads the user's login token by asking the app's own internal JavaScript helper for it. The token grants the attacker the same access the user has, including reading balances and placing orders that move funds. The whole chain finishes in under two seconds and the user only sees a "Verification Complete" screen.

The Vulnerability

The app had two deep link entry points that ended up loading URLs in the same in-app browser, but only one of them validated the URL.

  • The custom-scheme entry point (exchange://web?url=...) validated the URL against a domain allowlist of the exchange's own domains.
  • The intent-extra entry point read a url string extra delivered through Android's intent:// URI scheme. The router only checked that the value started with "http" and skipped the domain validation entirely. It then opened the in-app browser with the JavaScript bridge enabled.

Because Android's intent:// URI scheme lets any web page deliver string extras to an exported activity, an attacker could fire the bypassing path from any HTTPS site with no co-installed malicious app required.

Once the attacker's page loaded inside the WebView, the in-app JavaScript bridge exposed a getToken method that returned the user's authentication token from local storage with no origin check. The bridge was reachable through a window prompt hook used by the app's WebView client.

The Attack

  1. The attacker hosts a delivery page with an intent:// link whose S.url extra is the attacker's own URL. A separate attack page hosts the bridge exploit JavaScript.
  2. The victim, logged into the app, taps the link in any browser, email, or messaging app.
  3. The browser fires an intent that delivers the attacker URL as a string extra to the app's main activity.
  4. The internal router sees the URL starts with "http" and opens it in the in-app browser with the JavaScript bridge attached. The domain allowlist does not run on this path.
  5. The attack page calls window.prompt with a JSON payload that the bridge interprets as a request to call getToken. The bridge returns the auth token.
  6. The token is exfiltrated to the attacker server. The page then uses it server-side to pull the victim's account info. The victim sees a "Verification Complete" screen.

Confirmed token power

With the captured token the attacker reached, on the live API:

CapabilityEndpoint behaviour
Read account identity (user ID, masked email, last login IP, KYC status)Returned full PII payload
List spot accounts and balancesReturned all balances
Read full DEX wallet portfolioReturned 136 currencies and total value
Read futures account, margin, unrealised P&LReturned, confirming token works across sub-domains
List OTC merchant profile and saved bank accountsReturned
Place spot orderReturned parameter validation error, not 401, confirming write access
Place DEX swap orderReturned parameter validation error, not 401
Add or modify saved payment methodAccepted by API

None of the tested endpoints required additional 2FA at the API layer. The token alone was sufficient to read account data and submit write operations.

The Impact

For any funded account, the attacker can drain funds via unfavourable trades placed against an attacker-controlled counter-account, create P2P trades, and add their own bank account as a saved payment method. Every read endpoint that returns PII is also reachable, which on its own is a serious incident for a regulated exchange.

The delivery vector is a single link, so the attack scales without any infrastructure beyond a phishing domain. Any logged-in user who taps the link is vulnerable, no matter where the link is delivered or how the app was installed.

Remediation

  • Apply the same domain allowlist to every code path that opens the in-app browser, including the intent-extra entry point. Reject any URL that is not on the allowlist.
  • Treat the in-app JavaScript bridge as privileged. Only inject it for pages whose origin is on the trusted allowlist, and check the origin again inside every bridge method.
  • Make getToken and any other sensitive bridge methods refuse to respond to non-trusted origins, even if the bridge is somehow attached.
  • Add android:autoVerify="true" to all custom-scheme intent filters and prefer App Links over custom schemes for any URL that loads in a privileged WebView.
  • Bind issued tokens to a device or session so a stolen token cannot be replayed from another network or device, and require step-up confirmation for sensitive actions like adding a payment method.