00What this application is, and why it matters
The target was a password and secrets manager built for the cryptocurrency world, delivered as Android and iOS applications: a vault that stores the logins, keys and recovery material that gate access to digital assets. In a normal application, a breach means exposed data. In a vault like this, the contents are the money. Access to the vault can mean access to irreversible, bearer-style assets, where a single successful theft has no chargeback, no reversal and no insurer of last resort.
Because it is a mobile product, some of the vault lives on the device. The apps stored user data locally and protected it with client-side encryption, which puts the secrecy of a key in the one place an attacker fully controls: the handset in their hand. The engagement covered both app binaries, the data they left on the device, and the backend API they talk to. Around that sat tiered subscriptions with premium vault features, multi-factor authentication, management of user devices and sessions, an administrative console, and a cloud backend behind a web application firewall. On the surface, a serious security posture for a serious product.
The assessment told a different story. None of the individual issues was exotic. Chained together, they took the engagement from no account at all to administrative control and unauthenticated data exposure, against the one class of product that can least afford it. This is the walk-through, sanitised: no organisation, product, person, host or credential is named, and the explanation stays at the level of technique and lesson rather than a runbook.
01The kill chain at a glance
02Stage one: a key in two halves, and a leaking pipeline
The intrusion began before any login attempt, in the app itself. Because the vault encrypts user data on the device, the key that protects it has to exist somewhere the app can reach, and static analysis of the Android and iOS binaries is where that search starts.
The design tried to hide the field-encryption key by splitting it in two. One half was hardcoded in the application. The other was not shipped in the binary at all; instead the app carried a hardcoded URL pointing to a public repository of a third-party library, which hosted the second half. Pulling the app apart revealed the first half and the pointer; following the pointer gave up the second. Combined, they reconstructed the key that protected the data stored on the device. Splitting a secret across two readable locations is obfuscation, not protection: both halves were ultimately in the attacker's reach.
Separately, reconnaissance of the target's build pipeline surfaced further hardcoded secrets that had never been rotated and were still valid at test time, including application API keys and an OIDC private signing key. Leaked secrets are common; unrotated leaked secrets with cryptographic weight are the dangerous kind. A reconstructable field-encryption key undermines the confidentiality of on-device data, and an OIDC signing key undermines the trustworthiness of the identity tokens the application issues. Before a single credential was guessed, the engagement already held cryptographic material and authenticated API access.
03Stage two: an authentication layer with no brakes
With the surface mapped, the registration endpoint gave away which email addresses already had accounts, through small differences in how it responded to known versus unknown addresses. On its own, user enumeration is low severity. What made it matter was the next gap: the authentication endpoints had no rate limiting. Nothing throttled or locked out repeated attempts, which turned every authentication step into something an attacker could test at volume. That absence is what set up the stage that followed.
04Stage three: the MFA bypass, explained
Multi-factor authentication is meant to be the backstop after credential compromise. Its entire value rests on one assumption: that an attacker who has valid credentials still cannot satisfy the second factor without the user's device or token. In this product, that assumption did not hold. Because the MFA issue is the hinge of the whole chain, it is worth explaining as a vulnerability class, without any sensitive detail.
Vulnerability deep-dive · MFA bypass
Class of weakness
The bypass sits at the intersection of an authentication-flow logic flaw and missing rate limiting. Neither alone is as damaging as the two together; they compound.
How it works, conceptually
Web frameworks usually enforce brute-force protection through middleware or a library on authentication endpoints. When that protection is applied inconsistently, guarding the password step but not the second-factor step, or when its enforcement can be influenced by client-controlled input, the protection effectively disappears for the step it misses.
Here the verification step for the second factor did not carry the same protection applied elsewhere in the flow. That asymmetry is the flaw: the first factor was guarded, but the second-factor check could be exercised freely. Short, time-based codes have a small character space and a brief validity window, so without a lockout or throttle on that step, systematically working through the valid space inside the window becomes a realistic attack rather than a theoretical one.
Why it matters
MFA is typically the last line of defence after a credential is phished, leaked or reused. Bypassing it quietly reduces the whole model to single factor, so leaked or reused credentials alone become sufficient, which is exactly the outcome the control existed to prevent.
Root cause
The protective mechanism relied on a library responsible for tracking and limiting authentication attempts, and that enforcement was not applied consistently across every step of the multi-factor flow. The second-factor verification was left exposed to unrestricted testing.
With the second factor neutralised, the engagement held an authenticated session. From here, the assessment moved from breaking in to abusing what being inside allowed.
05Stage four: the price of a premium tier was a parameter
During registration, the subscription tier was decided by the client, with no server-side enforcement behind it. Choosing an elevated package was simply a matter of asking for it, which granted premium vault features without any payment. This is classic business-logic abuse: the server trusted the client to state what it was entitled to, and the client is the one place an attacker fully controls. Beyond the obvious revenue impact, premium tiers often unlock functionality that widens the attack surface further.
06Stage five: admin actions from a standard account
Authenticated as an ordinary user, requests were replayed with modified identifiers aimed at administrative endpoints. Broken access control did the rest: the server enforced no privilege check, so a standard account could trigger admin-level device actions and pull audit and security logs meant only for administrators. Authorisation that is assumed in the interface but never enforced at the API is one of the most reliable ways a normal user becomes an administrator.
Every layer trusted the one before it. The client picked its tier, the API trusted the client, and the admin functions trusted the API.
07Stage six: the infrastructure underneath
The administrative console and its API were reachable from arbitrary addresses, with no network restriction placing them behind a trusted boundary. Behind them, default database credentials were still active, granting direct access to the backend data store, and the perimeter firewall's ruleset was weak enough to bypass. A management plane exposed to the whole internet, protected by a password that ships in the box, is the point where an application issue becomes an infrastructure one.
08Stage seven: impact with no login at all
Finally, two issues were confirmed without any authentication, independent of the session work above:
- Server-side request forgery. An endpoint accepted attacker-supplied URLs and made requests from the server, enabling probing of the internal network from the outside.
- Unauthenticated file download. Archive files held in cloud object storage could be retrieved by anyone with a valid URL, and those URLs were obtainable from ordinary API responses, so stored data was reachable with no login.
These mattered on their own, but in context they close the loop: even a visitor who never cleared the front door could reach internal services and stored files.
09Why each link held
| Stage | Root cause | Vulnerability class |
|---|---|---|
| Encryption key | Field-encryption key split across the app binary and a public repo; pipeline secrets unrotated | Insecure key storage |
| Enumeration | Differential responses reveal valid accounts | Information disclosure |
| No rate limiting | Auth steps not throttled or locked out | Missing anti-automation |
| MFA bypass | Rate-limit protection not applied to the second-factor step | Auth logic flaw |
| Subscription abuse | Client-controlled tier, no server-side check | Business-logic flaw |
| Admin access | No server-side authorisation on admin endpoints | Broken access control |
| Infrastructure | Exposed console, default DB credentials, weak firewall | Security misconfiguration |
| Unauth impact | Server-side fetch of attacker URLs; unauthenticated downloads | SSRF / access control |
10What would have broken the chain
- Never ship keys in the app, split or whole. Anything in a mobile binary, or in a public resource it points to, is readable. Derive encryption keys at runtime, bind them to the platform keystore and secure enclave, and treat splitting a secret across readable locations as obfuscation, not a control.
- Protect on-device data properly. Use the OS keystore and hardware-backed encryption for anything stored locally, and minimise what the app persists on the device at all.
- Treat the pipeline as production. Keep secrets out of code and build systems, scan every commit, and rotate anything exposed immediately. Give cryptographic keys the shortest viable life.
- Rate-limit the whole authentication flow, not most of it. The MFA bypass existed because protection was applied unevenly. Throttle and lock out every step, the second factor included, and never let client input influence that enforcement.
- Decide entitlements on the server. Subscription tier, like every authorisation decision, belongs to the server, never the client.
- Enforce authorisation at the API, object by object. Check privilege on every administrative action and record, not just in the interface.
- Put the management plane behind a boundary. Restrict admin consoles to trusted networks, remove default credentials everywhere, and validate firewall rules against real bypass testing.
- Close the unauthenticated edges. Validate and restrict server-side fetches to stop SSRF, and require authorisation on file access rather than relying on URL secrecy.
The individual flaws here are the familiar furniture of application security. The lesson of a vault is that chaining still works, and when the product guards irreversible assets, a chain that ends in admin and unauthenticated data access is not a finding to schedule, it is the whole risk.