← Back to all reports

Exchange Deep Link Loads Arbitrary URL in Authenticated In-App Browser, One-Click Credential Phishing

Reported Mar 4, 2026
Severity High
Platform Android
Vulnerability Class Deep Link Injection (CWE-939)
Target Type Centralized Cryptocurrency Exchange
Impact One-tap in-app credential phishing from any web page

The Risk

An attacker could send a single link by SMS, email, or chat that opens the exchange app and shows a fake login screen inside the real app, with the exchange's title bar, logo, and navigation visible above it. The fake page captures the user's email and password the moment they tap submit, and then quietly drops them onto the real exchange so nothing looks wrong. There is no malicious app to install and no warning the average user will recognise.

The Vulnerability

The exchange's Android app registered a custom URL scheme that routed to a generic in-app browser activity. The handler accepted a url parameter and loaded it directly with no domain allowlist. The activity also injected the native JavaScript bridge into every page it loaded.

Three issues compounded:

  • The url parameter was not validated against any allowed domain list before being passed to the WebView.
  • None of the app's custom URL schemes used android:autoVerify, so the operating system did not check domain ownership before routing the link to the app, and Chrome opened the link without any app chooser dialog.
  • An "unauthorized page" warning banner did appear at the top of the WebView, but it was a single-tap dismiss and did not block interaction with the page below it. The native JavaScript bridge was injected regardless of this flag.

The Attack

  1. The attacker hosts a trigger page on any HTTPS site, branded to look like a legitimate exchange notice ("Account Access Restricted" with a "Verify and Restore Access" button).
  2. The victim taps the button. The page fires an intent:// URL that Chrome routes directly to the exchange app, with no app chooser.
  3. The exchange app opens its in-app browser activity and loads the attacker URL. The page renders inside the genuine app shell. The real exchange title bar, logo, and back button sit above it.
  4. The attacker server detects the warning-flag query parameter the app appends and serves a phishing page styled to match the real login screen, with email and password fields.
  5. The user enters credentials and taps Next. The credentials are exfiltrated via fetch and a fallback image beacon. The WebView is then redirected to the real exchange home page so nothing looks out of place.

The attack works from any browser on any Android device with the app installed. No co-installed malicious app is needed. The link can be delivered through any channel that renders a clickable URL.

The Impact

The attacker page is visually indistinguishable from the real exchange login screen because it lives inside the real app's frame. Users who recognise phishing on regular websites by checking the URL bar have no equivalent signal here, since the visible chrome is the exchange's own. After the user taps submit, the phishing page redirects the WebView to the real exchange home, so the victim has no clear sign that something was captured.

Confirmed during testing on the production app version:

  • Tapping a single link in Chrome opened the exchange app and rendered the attacker page inside it.
  • The native JavaScript bridge was attached and reachable from the attacker page.
  • Email and password were captured server-side on a controlled test account in under two seconds end to end.
  • The post-capture redirect dropped the victim onto the real exchange front page with no anomaly.

For a centralized exchange where the email and password give access to spot, futures, and withdrawal flows (subject to whatever 2FA the user has configured), credential capture at this scale is high-impact, and the link-only delivery makes it trivial to mass-distribute.

Remediation

  • Validate the url parameter against a hard allowlist of exchange-owned domains before loading anything in the in-app browser. Reject everything else.
  • Do not inject the native JavaScript bridge for pages flagged as external or unauthorised. The app already detects these via its own warning flag, so it can strip the bridge for them.
  • Replace the dismissible warning banner with a hard interstitial that requires explicit confirmation before the page is interactive, displaying the full domain.
  • Add android:autoVerify="true" to all custom-scheme intent filters so domain ownership is enforced and other apps cannot register competing handlers.
  • Treat any in-app browser load of an attacker-supplied URL as untrusted: no cookies, no bridge, no shared session.