Hardcoded Branch.io Key Enables Official Branded Phishing Links
The Risk
An attacker could create unlimited links under a major postal service's official verified domain and send them to anyone via SMS, email, or even printed QR codes on fake delivery notices. These links were indistinguishable from legitimate communications and opened the real app on the victim's phone. Inside the app, a fake login page could capture the victim's credentials with no visible indication that anything was wrong.
The Vulnerability
1. Hardcoded Branch.io live SDK key
The Android app contained a Branch.io live SDK key stored in plaintext in its resources. This key was not a test or debug key; it was the production key used to create deep links under the organization's official verified domain. Anyone who extracted this key from the APK (a trivial operation requiring no root access) could create unlimited deep links via the Branch.io API.
// Extracted from app resources
key_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXX The Branch.io API accepted this key with no additional authentication, rate limiting, or IP restrictions. Hundreds of links could be created in minutes.
2. Verified App Link domain with no payload restrictions
The organization's deep link domain had a valid assetlinks.json configuration, meaning links opened the app directly on Android with no browser disambiguation dialog. Users never had an opportunity to inspect the URL. The deep link payload (the $deeplink_path parameter) was fully attacker-controlled and passed directly to the app's internal deep link router with no validation.
3. Internal WebView with no URL allowlist
The app contained an internal browser activity registered as a deep link destination. This activity loaded any URL with JavaScript enabled, DOM storage enabled, third-party cookies accepted, and no URL bar visible to the user. The victim saw only a back arrow and the organization's name in the title bar. There was no domain allowlist, so attacker-controlled pages rendered inside the app's chrome indistinguishable from legitimate content.
4. Integration bug masking the full chain
On non-rooted devices, the full remote chain (Branch link to app to internal browser) reached the target activity but crashed due to a type mismatch in the app's dependency injection framework. A non-security analytics parameter was expected as an integer but arrived as a string via the deep link router. This crash was an unintentional integration bug, not a security control. If fixed as part of routine development (a one-line annotation change), the full remote phishing chain would become immediately exploitable.
The Attack
- The attacker extracts the Branch.io live key from the app's resources
- Using the Branch.io API, the attacker creates a deep link under the organization's verified domain. The
$deeplink_pathis set to open the internal browser with an attacker-controlled URL - Optionally, the attacker generates QR codes via the Branch.io QR code API for physical attack vectors such as fake delivery notices or parcel locker labels
- The victim receives the link via SMS, email, or scans a QR code. The link appears under the organization's official domain with valid TLS
- Tapping the link opens the real app directly (verified App Link, no browser dialog). The internal browser loads a pixel-perfect clone of the organization's login page
- The victim enters their credentials. No URL bar is visible; the title bar shows the organization's name
- Credentials are captured on the attacker's server. The victim is redirected to the organization's real homepage with no indication of compromise
Physical attack vectors
The Branch.io QR code API allowed the attacker to generate branded QR codes that could be printed on physical media. For a postal/logistics company, this enables attacks via fake delivery notices, parcel locker labels, or return-to-sender cards. The QR codes resolve to the organization's verified domain, passing any inspection by a cautious user.
The Impact
| Capability | Result |
|---|---|
| Create unlimited official deep links | Confirmed - no rate limiting |
| Generate branded QR codes | Confirmed - via Branch API |
| Open app directly (no browser dialog) | Confirmed - verified App Link |
| Load attacker page in app chrome | Confirmed - no URL bar visible |
| Capture credentials | Confirmed - test credentials captured |
| Seamless post-capture redirect | Confirmed - victim redirected to real site |
Anti-phishing detection validates realism
During testing, the proof-of-concept phishing page was independently flagged and taken down by an automated anti-phishing detection service. This confirms that the phishing page was realistic enough to trigger real-world automated defenses, validating the severity of the attack chain.
Remediation
- Rotate the Branch.io live SDK key and restrict link creation via the Branch dashboard using IP allowlists and rate limiting
- Add a domain allowlist to the internal browser activity so only first-party domains and known partner domains can be loaded
- Validate deep link payloads in the app's link router and reject URIs containing external URLs
- Fix the dependency injection type mismatch only after implementing the domain allowlist, as fixing the crash without adding URL validation makes the remote chain fully exploitable
- Display the loaded URL prominently in the internal browser so users can identify pages that are not from the organization