Press enter or click to view image in full size
Broken Authentication has been on OWASP’s radar for years — yet it remains one of the most exploited vulnerability classes in real-world applications. Why? Because developers focus on building features, and security becomes an afterthought applied inconsistently — especially across different API versions.
In this walkthrough, I’ll show you exactly how a missing rate limit on an OTP endpoint allowed me to brute-force my way into any account — completely bypassing authentication. I’ll walk you through the pentester’s thought process at every step — what to look for, why it matters, and what to do when the server pushes back.
Step 1: Reconnaissance — Don’t Login, Explore
The first rule of any engagement: never rush to the obvious attack. Instead of trying to brute-force the login directly, I scanned the page for alternative authentication flows. That’s when I spotted the “Forgot Password” option.
This is a pentester’s instinct — password reset flows are almost always weaker than the main login. They are written separately, tested less rigorously, and introduce entirely new code paths. Every feature in a reset flow — OTP delivery, email verification, token generation — is another potential crack in the wall.
What to look for at this stage:
Clicking “Forgot Password” brought up an email input field.
Step 2: Triggering the OTP — Setting the Stage
I entered the target’s registered email and clicked “Send OTP.” The app confirmed: “OTP has been sent to your registered email.”
I had access to that inbox — but let’s think like an attacker for a moment. All they see is this success message. No inbox, no OTP, nothing. But the server just confirmed the email exists and an OTP is on its way. For an attacker, that’s enough. The real question now is whether that OTP is short enough to guess and whether the server will let them keep trying. Two simple conditions — and if both are true, this “Forgot Password” button just became a full account takeover vector.
Press enter or click to view image in full size
With Burp Suite running as a proxy, I entered a random OTP and a test password on the Reset Password form, then hit submit. The app returned “Invalid OTP.” — expected, since I guessed randomly.
Press enter or click to view image in full size
Before attempting anything further, I needed to see what the OTP verification request looks like at the network level. This is where Burp Suite comes in — it sits between your browser and the server, capturing every request and response passing through. Think of it as a window into the conversation your browser is having with the application.
With Burp running as a proxy, I entered a random OTP and a test password on the reset form and hit submit. The app returned “Invalid OTP” — expected. But what I really wanted was what Burp captured in the background:
This one request tells the whole story:
Plain numeric OTP, no token binding, no visible protection — the only thing standing between me and success is rate limiting. Time to find out if it exists.
Press enter or click to view image in full size
Step 4: Setting Up the Brute Force — Burp Intruder
I sent the captured request to Burp Suite’s Intruder — a built-in tool designed for automated, customized attacks. Think of it as a robot that sends thousands of requests for you, changing one value each time.
Join Medium for free to get updates from this writer.
I marked the otp value as the injection point — the position Intruder will cycle through payloads on. I configured the payload type as Numbers, ranging from 0000 to 9999, covering every possible 4-digit OTP. Then I launched the attack.
Press enter or click to view image in full size
Step 5: First Attack — Reading What the Server Tells You
I watched the response length column throughout — a consistent length means the same “Invalid OTP”, anything different means something changed.
First few requests held steady at 507. Around the 8th attempt it dropped to 483 and shifted to {"message":"ERROR..."}. That's not a failed attack — that's the server catching me and blocking my IP. Rate limiting exists on v3, but in doing so it revealed something far more useful: its security is version-specific. What about v2?
Press enter or click to view image in full size
Press enter or click to view image in full size
Press enter or click to view image in full size
Step 6: API Versioning — The Security Gap Nobody Patches
The /v3/ in the path is not just a version number — it's a window into the application's history. A pattern I've seen repeatedly: companies build v1 with minimal security, patch v2 and v3 when issues are found, apply rate limiting and lockout to the new version — and forget to take down the old ones. That's Improper Assets Management (OWASP API9), and it pairs catastrophically with broken authentication.
Press enter or click to view image in full size
Step 7: Switching to v2 — Zero Protection
Before switching to v2, I closed the session and triggered a fresh OTP. Old OTPs expire — running a brute force against a stale token is wasted effort regardless of rate limiting. Fresh request captured, endpoint changed to /v2/check-otp, attack launched.
I went through the Forgot Password flow again, captured the fresh request, modified the endpoint from v3 to v2 in Intruder, and launched the same attack — Numbers 0000 to 9999.
Press enter or click to view image in full size
This time the results looked completely different. The server answered every single attempt. The v2 endpoint had zero rate limiting — exactly what I suspected.
Press enter or click to view image in full size
Unlike v3, the server responded to every single attempt without interruption — no ERROR responses, no dropped connections, no change in status code or response length indicating a block. The v2 endpoint had zero rate limiting
Press enter or click to view image in full size
Step 9: Account Takeover — Confirmed
Once the attack completed, I sorted the results by response length in ascending order. Every request returned 507 bytes — except one, sitting at 488 bytes.
I clicked on it
The response showed:
Press enter or click to view image in full size
OTP 6674 was correct. Since the new password was already supplied in the original request, the account password was now mine. I logged straight into the target account — full takeover, complete.
This wasn’t a single bug — it was several weaknesses stacked together. A 4-digit OTP with only 10,000 combinations, no rate limiting on legacy endpoints, no account lockout, and the attacker controlling the new password in the same request. Any one of these alone is a risk. Together, they’re a full account takeover waiting to happen.
The most dangerous vulnerabilities are never the loud, obvious ones — they’re the quiet ones. A forgotten version, a missing rate limit, a 4-digit number. As a security researcher, your job is to find what the developers forgot. And they almost always forgot the old version.