Authentication Bypass in In-App Browser Domain Validator, One-Tap Session-Token Theft and Full Account Takeover
The Risk
One tap on a link inside the app's social feed was enough to silently hand the victim's logged-in session to an attacker. With that session the attacker could read the victim's full identity profile (legal name, home address, phone, email, identity-verification status), see their money-movement history and bank details, kick off a phone-number change as the victim, and log the victim out of their own sessions. No password, no second factor, no further taps from the victim were needed.
The Vulnerability
The Android app's in-app browser auto-injected the logged-in user's session tokens into the loaded page's local storage on every navigation, gated by a domain validator. The validator was a substring check:
_isValidDomain(n) {
return whiteListDomains.some(r => n.indexOf(r) > -1);
} The allowlist contained entries like https://www.brand.com and a handful of regional variants. Because the check only asked whether one of those strings appeared anywhere in the URL, any URL that contained the entry as a substring passed, regardless of the actual host.
On every page load event, the in-app browser executed an auto-login script that wrote a base64-encoded login data blob (containing an access token, an authentication token with a 30-day lifetime, a device token, and an anti-CSRF token) into the page's local storage. With the validator bypassed, those tokens landed on the attacker's origin.
Bypass forms (all pass the substring check)
https://attacker.example/?x=https://www.brand.comhttps://www.brand.com.attacker.example/https://attacker.example/#https://www.brand.comhttps://attacker.example/path/https://www.brand.com/x- URL shorteners wrapping the same payload
The validator fired on every loadstart, including after redirects, so a shortened link triggered the same exfiltration as the raw URL.
The Attack
The delivery surface was the app's own social feed. Any logged-in user could post a link, and any other logged-in user who tapped it would have their tokens exfiltrated.
- The attacker posts a link, raw or shortened, that contains the brand domain as a substring
- The victim taps the link from inside the app's feed
- The in-app browser opens the URL, runs the validator, and passes
- The auto-login script writes the login data blob into the attacker page's local storage
- The attacker page reads its own local storage, decodes the blob, and posts the tokens to the attacker's server
- The page renders a generic "Verified" screen so the user sees nothing unusual
End-to-end timing was under five seconds from tap to a fully-captured session on a researcher-owned account.
The Impact
The captured tokens were replayed from a separate machine, with a different IP and a different TLS client, against the researcher's own account. State-change endpoints were probed with empty bodies only to confirm the auth gate passed; no real mutation was executed.
Identity and KYC read
The customer profile API returned full identity-verification PII: legal first and last name, home street address, city, postcode, country, phone number with country code, and email. On a funded account the same schema returns bank accounts, card metadata, and deposit history.
Account state and ledger
Identity, money-movement ledger (deposits, withdrawals, fees, credits), account equity, billing state, and identity-verification status were all readable with the stolen token.
Phone-change initiation
The account-recovery phone-change endpoint accepted the stolen token. Supplying an attacker phone number would issue an SMS verification code to that number, completing the phone change and opening a path to password-reset takeover.
Session revoke
The session-revoke endpoint also accepted the stolen token. The exfiltrated device token satisfied the device binding, so the attacker could revoke the victim's own sessions while keeping their stolen one.
Tokens were not bound to device or IP. The access token had a 24-hour lifetime; the authentication token had 30 days.
Remediation
- Replace the substring check with a strict URL parse and exact hostname comparison against a hard-coded list
- Stop auto-propagating session tokens into any non-first-party origin. Even with a correct allowlist, writing login data into another page's local storage on every navigation is fragile by design
- Shorten the access-token lifetime (currently 24 hours) and bind tokens to a device fingerprint server-side so replay from a different client is rejected
- Add explicit origin checks to every cross-origin message handler used inside the in-app browser
- Rotate all in-flight tokens after the fix ships