The Password Reset Token Leak
When you think about authentication, the first things that come to mind are usually strong passwords, encryption, or multi-factor security. But what if the real problem isn’t a missing cipher or a weak password? What if it’s the logic behind how you handle sensitive data that blows your security wide open?
One classic example? Password reset flows that leak tokens insecurely breaking the whole authentication logic, no matter how tight your password rules are.
The Logic Flaw: Leaking Reset Tokens in API Responses
Password reset tokens are supposed to be secret. But in some apps, when you request a reset, the server sends the token right back in the HTTP response. That means anyone who can see that response whether a hacker sniffing your Wi-Fi, a rogue browser extension, or someone with access to your proxy logs can grab the token and hijack the account.
This isn’t just a bad idea. It’s a fundamental logic flaw:
-
You’re trusting the client to keep the token secret.
-
You’re exposing a sensitive credential through an insecure channel.
-
You’re breaking the authentication flow by giving attackers a shortcut to reset passwords without ever logging in.
Why This Breaks Authentication Logic
Authentication relies on secrets. Reset tokens are one-time secrets meant to verify that only the rightful user can reset their password. But if those secrets are freely shared in API responses, the attacker’s path becomes effortless.
In short: you can have the strongest password hashing, but if you leak your reset tokens, your entire login security is toast.
Breaking Down Authentication: Exploiting the Password Reset Feature
Alright, let’s dive into the authentication flow on the HackMe website and see how we can escalate from a normal user to the admin.
Imagine the login page looks like this:
Welcome to HackMe Website
Login | Reset Password
For starters, we have these normal user credentials:
user@hackme.com / password123
Logging in with these works fine, and after login, we get this message:
Welcome user@hackme.com! You are logged in as a normal user. Logout
Cool, but we want to level up specifically, get admin access.
So, let’s check out the Reset Password option. The interface asks for an email:
Reset Password
Enter your email:
We swap out user@hackme.com with admin@hackme.com and hit send. The site responds:
If your email exists, a password reset token has been sent.
Pretty standard, right? But what’s going on behind the scenes? Time to dig deeper.
We repeat the request but this time, monitor it closely using Burp Suite. On the left, we see the HTTP request, and on the right, the response.
If we inspect carefully, there’s a password reset token inside the response, along with an endpoint URL.
Now, here’s the juicy part using that token, we access the reset endpoint:
Confirm Password Reset
Reset Token: [token here]
New Password: [new password here]
By plugging the token in and setting a new password, we effectively reset the admin password and gain full access.
This is a classic example of how insecure password reset mechanisms can be abused to escalate privileges. Always check how tokens are generated and validated. Weak flows like this can be your golden ticket.
JWT Vulnerability: Bypassing Role-Based Access
In this example, we’re dealing with a login interface that looks pretty normal on the surface but has a nasty misconfig behind the scenes.
🧪 Credentials
You can log in using:
Username: user
Password: userpass
Once you’re in, you land on this dashboard:
Welcome, user!
Your role: user
[ Go to Admin Panel ]
But when you click Go to Admin Panel, you get smacked with:
Access Denied
You are not an admin.
[ Back ]
So far, standard RBAC stuff, right? Let’s peek under the hood.
Token Inspection (Burp Suite)
Open up Burp Suite and log in with the test creds. Go to HTTP History and look for a request to /dashboard
. Burp will pick up a JWT token automatically thanks to the JWT Editor extension.
You’ll see something like this in the Cookie header:
eyJhbGciOiAibm9uZSIsICJ0eXAiOiAiSldUIn0.eyJ1c2VybmFtZSI6ICJ1c2VyIiwgInJvbGUiOiAidXNlciJ9.
Decode it, and you’ll find:
Header:
{
"alg": "none",
"typ": "JWT"
}
Payload:
{
"username": "user",
"role": "user"
}
Red flag spotted: alg: none
. This means the server isn’t verifying the JWT’s signature. We can forge our own token. Let’s do it.
Exploitation: Become Admin
Change the role
in the JWT payload to "admin"
:
New Payload:
{
"username": "user",
"role": "admin"
}
Re-encode the JWT without a signature (keep alg: none
) and replace the token in your browser using a cookie editor or Burp Repeater.
Your forged token might look like:
ewogICAgImFsZyI6ICJub25lIiwKICAgICJ0eXAiOiAiSldUIgp9.ewogICAgInVzZXJuYW1lIjogInVzZXIiLAogICAgInJvbGUiOiAiYWRtaW4iCn0.
Go back to /admin
, refresh the page boom, you’re in.
Admin Panel
🚩 FLAG: FLAG{jwt_admin_escalation_success}
Why This Matters
This is a real-world vulnerability. A lot of devs incorrectly implement JWTs and allow alg: none
, thinking the payload is safe. But if you don’t validate the signature, you’re literally letting anyone forge roles, identities full takeover.
Bypassing 2FA: How I Sneaked Past OTP with a Simple Bug
Alright, so imagine this: we already cracked the admin password classic admin:adminpass
.
But boom! There’s a 2FA in place, and it’s asking for an OTP code. For this demo, I simplified it to a 3-digit OTP just to keep things chill. I’m running a Python debug script that simulates the OTP being sent to the admin’s email, so I can spy on the code before it hits their inbox.
Next up, the login page asks for the OTP and gives you an option: “Remember this device,” so next time, no OTP hassle.
Now, after some failed OTP tries, it says:
Too many OTP attempts. Try again in 60 seconds.
Classic rate limiting. But let’s peek under the hood using Burp Suite to see what’s really going on with the requests.
Notice the parameter:
remember_device=on
This tells the server to remember your device and skip OTP in the future.
Here’s the trick: what if we send the param but empty? Like:
remember_device=
When I do that, no more rate limiting. The server isn’t blocking attempts anymore.
On the debug side, the OTP code is logged like this:
[DEBUG] OTP for admin: 382
Since it’s only 3 digits, I scripted a quick brute force from 300
to 400
keeping it tight to avoid long waits.
With this, I eventually hit the right OTP: 382
. After the correct OTP, the session key updates, and now I’m fully logged in.
Why this matters:
- Rate limiting bypass: The bug with
remember_device
lets us spam OTP attempts without waiting.