← Back to all reports

Carrier App Open Redirect + Exposed Deep Link Key Enables In-App Phishing

Reported Apr 9, 2026
Severity High
Platform Android
Vulnerability Class Content Injection Chain (CWE-601, CWE-798, CWE-20)
Target Type National Mobile Carrier
Impact Pixel-perfect in-app credential phishing

The Risk

Any customer of a national mobile carrier could lose their account login by tapping a single message. The link arrived on what looked like the carrier's own verified web address. Tapping it opened the real carrier app, then loaded a fake login page that looked identical to the real one, with the carrier's own logo and no web address bar visible. Anything the customer typed went straight to the attacker. There were no warning signs anywhere in the experience.

The Vulnerability

Three independent flaws in a national mobile carrier's Android app and web property combined into a remote credential phishing chain that rendered attacker-controlled HTML inside the official app's WebView with no URL bar visible. Fixing any one of the three components broke the chain.

1. Hardcoded deep link service key

The production key for the carrier's deep link service was stored XOR-encoded inside a native library shipped with the app. Extraction did not require a device. A short script reading the library file and applying the XOR salt printed the key as plaintext.

The deep link service accepted that key with no further authentication and no rate limiting, allowing unlimited creation of links under the carrier's own verified app subdomain with attacker-chosen aliases and Open Graph metadata.

2. WebView URL injection in the article deep link handler

The article deep link handler read a url query parameter from the incoming intent and loaded it directly into the app's internal WebView with no host validation. An upstream check existed but only validated that the host of the incoming URL matched the carrier's own domain. Pointing that URL at the open redirect on the same domain (see below) passed the upstream check, and the WebView followed the resulting redirect to the attacker's domain.

3. Unauthenticated open redirect on the carrier's domain

The logout endpoint on the carrier's main website returned a 302 redirect to whatever URL was passed in the TARGET query parameter, with no authentication, no cookies, no allowlist, and no domain check. Verified with a single curl request using an Android WebView user-agent.

The Attack

  1. The attacker extracts the deep link service key from the public app's native library
  2. The attacker creates a link under the carrier's verified app subdomain via the deep link service API, with carrier-branded preview metadata
  3. The link's payload contains a URL pointing at the carrier's open-redirect logout endpoint, with the redirect target set to the attacker's phishing page
  4. The attacker sends the link to the victim by SMS, email, or a social media DM. The link previews with carrier branding and the carrier's own verified domain
  5. The victim taps. The carrier app opens via Android App Links. The article handler routes the deep link and instructs the WebView to load the carrier-domain logout URL
  6. The carrier's server returns a 302 to the attacker's domain. The WebView follows the redirect
  7. The attacker's pixel-perfect login page renders inside the app's WebView. The app's own navigation chrome, with the carrier's name in the title bar, is visible. There is no URL bar
  8. The victim enters their email and password and taps "Log in". Credentials POST to the attacker's server. The WebView is redirected back to the carrier's homepage after a brief delay, leaving no visible trace

Why the user has no defense

The link is genuinely on the carrier's verified subdomain. Android App Links open it directly in the app with no browser disambiguation dialog. Once inside the app, the WebView shows no URL bar, only the app's own back arrow and the carrier's name. The phishing page mirrors the real login layout exactly. Every visible signal points at the carrier.

The Impact

CapabilityResult
Create unlimited official deep linksConfirmed - no rate limiting, no auth beyond the leaked key
Open the real app via verified App LinksConfirmed - no browser dialog
Load attacker page in app chromeConfirmed - no URL bar visible
Capture credentialsConfirmed - test credentials captured live
Seamless post-capture redirectConfirmed - victim redirected to real carrier homepage

Confirmed end-to-end on a Pixel 5 (Android 13) with the production carrier app installed. Every request to the proof-of-concept server carried the carrier app's own request identifier, confirming the page rendered inside the app's WebView and not in a normal browser.

Delivery is fully remote and scales to any of the carrier's subscribers. No MITM, no root, no device prerequisites. The only requirement is that the victim has the official carrier app installed, which is true for the bulk of the customer base.

Remediation

  • Validate the TARGET parameter on the logout endpoint against an allowlist of carrier-owned domains. Reject any external domain. This is the single highest-impact fix and breaks the chain on its own
  • Call the existing host allowlist check in the article deep link handler before creating the WebView request. The allowlist function is already present in the codebase but is not invoked on this code path
  • Rotate the deep link service key. Move link creation server-side so a key never has to ship inside the app. Apply domain restrictions at the deep link service level to prevent unauthenticated link creation under the carrier's verified domain
  • Display the loaded URL prominently in the app's internal WebView so users can identify pages that are not from the carrier
  • Audit every internal WebView entry point for the same missing-allowlist pattern