How I Chained Four Bugs to Take Over Any Account on a Major FinTech Platform
Bug bounty hunting is a game of persistence. It’s about pulling on threads. Sometimes they snap, but every so often, you pull one and a whole security model unravels. This is the story of one of those times — a deep dive into the authentication flow of a major payment processor, dashboard.target.net
, and how a series of seemingly small flaws led to a full account takeover.
Thread 1: The Confirmation Bias — User Enumeration
Like any good hunt, it started with reconnaissance. The target was a complex financial services dashboard, so my first stop was the sign-up page. The application had a standard check to see if an email was already registered.
When I sent a non-existent email to POST /v1/merchants-portal/users/is-email-verified
, I got the expected error:
HTTP/2 400 Bad Request
{"status":{"error_code":"MERCHANTS_PORTAL_INVALID_TEMP_USER",...}}
But when I sent a known-good email, the response changed:
HTTP/2 200 OK
{"status":{"status":"SUCCESS",...}}
This is a classic user enumeration vulnerability. The server’s response confirmed whether an email was registered. More importantly, there was no rate-limiting or CAPTCHA on this endpoint. An attacker could use this to build a complete list of every user on the platform. This was the first crack in the armor.
Thread 2: The Login — CAPTCHA, PII, and a Fatal Flaw
With a list of valid users, the next logical step was the login form. The form was protected by Google reCAPTCHA, which is usually a strong deterrent against automated attacks like password spraying.
However, after capturing the login request in Burp Suite, I noticed something interesting. The recaptcha_token
was generated when the page loaded, not when I submitted my credentials. I confirmed that I could harvest a token from the page, wait, and then use it in a login attempt for a completely different user. The token was not tied to the session or the specific login attempt.
This meant the CAPTCHA could be bypassed at scale. An attacker could:
- Automate the collection of thousands of valid, single-use
recaptcha_token
s. - Use the user list from the enumeration bug.
- Launch a large-scale password spraying attack.
I tested this theory and found something even more interesting. When I successfully guessed a password, the server responded with a 200 OK
—but before it even asked for the 2FA code, it leaked a piece of the user's PII in the response:
{
"status": {"status":"SUCCESS"},
"data":{
"token":"d54a8267-7ec5-4c76-a0d5-c39dc00c4de0",
"phone_number": "1904",
"is_otp_verified":false,
...
}
}
A successful password guess not only validated the credential but also handed back the last four digits of the user’s phone number. The second crack was now a gaping hole.
Thread 3: The Final Gate — Bypassing 2FA
So, the attacker has a valid username and password and has gotten to the OTP entry screen. This is where most attacks should stop. The application uses a standard 6-digit code sent via SMS. A million possibilities. Un-brute-forceable, right?
Wrong.
The key to any 2FA system is not the complexity of the code, but the server-side logic that validates it. I sent the POST /v1/merchants-portal/users/login/otp-code
request to Burp Intruder to test for rate-limiting. I expected to get blocked after 5 or 10 attempts.
I didn’t.
I let the attack run. And run. And run.
After 13 hours, my Intruder attack was still going, having sent hundreds of thousands of requests without the session token being invalidated or my IP being blocked. To confirm, I stopped the attack and submitted the original valid OTP code I had received 13 hours earlier.
It worked.
The application had three critical flaws in its 2FA implementation:
- No Rate-Limiting: I could send unlimited guesses.
- No Session Timeout: The temporary token from the login step never expired.
- No OTP Code Expiration: The 6-digit code itself never expired.
This meant that bypassing the 2FA was not a matter of if, but when. An attacker could simply script this attack to try all one million combinations, guaranteeing entry.
The Kill Chain: From Zero to Full Account Takeover
When you chain these vulnerabilities together, you get a reliable, repeatable path to compromising any account on the platform:
- Enumerate Users: Hit the
/is-email-verified
endpoint to get a list of valid targets. - Password Spray: Use the list of valid users and a list of pre-harvested CAPTCHA tokens to find a user with a weak password.
- PII Leak: A successful guess immediately leaks the last 4 digits of the user’s phone number.
- Bypass 2FA: Use the session token from the successful login to brute-force the 6-digit OTP code at the
/otp-code
endpoint, which lacks rate-limiting. - Compromise: The attacker is now fully logged in.
As a final blow, the last response in the login flow also contained the user’s permanent API access_key
, giving the attacker persistent, scriptable access to the account long after the web session ended.
This was a fascinating journey through a complex application, and a powerful reminder that even the strongest-looking doors are only as secure as the logic that holds them together.