Free Link 🎈

I opened the site just to reset my password. Not hacking. Not hunting. Just forgot it again, like a normal person. Five minutes later, I was still in Burp. Thirty minutes later, I realized I was holding something I definitely shouldn't have.

That's how most real bugs start — not with "let's find a critical," but with "huh… that's weird."

🕵️ Recon: The Boring Part That Always Pays

I wasn't even targeting authentication that day. I was doing mass recon — endpoint mapping, JS scraping, diffing responses, the usual boring grind that nobody posts screenshots of.

While crawling unauthenticated flows, one endpoint kept showing up quietly in responses and JavaScript:

/auth/reset/continue

No UI reference. No documentation. Just… there.

Those endpoints are almost never useless.

🔍 Understanding the Reset Flow (Without Clicking Anything)

Instead of using the UI, I replayed the full password reset flow manually:

  1. Submit email
  2. Receive reset token
  3. Validate token
  4. Set new password

Nothing unusual at first. But when I intercepted the token validation response, I noticed something off.

The response returned a token like this:

{
  "status": "success",
  "reset_token": "eyJhbGciOi..."
}

I expected this token to be:

  • Single-use
  • Short-lived
  • Tied to the same session

It was none of those.

🔄 Token Reuse: The First Real Crack

I reused the same token:

  • In a different browser
  • From a logged-out state
  • After already resetting the password once

Still valid.

That alone was bad, but I wanted to see how strict the backend really was. So I changed one thing.

I kept the same token, but changed the email parameter.

It worked.

No validation that the token belonged to that email. No cross-check. Just blind trust.

At this point, it was a clear password reset logic flaw.

🧨 Turning One Reset into Many

The password update request looked like this:

POST /auth/reset/continue
{
  "email": "victim@example.com",
  "reset_token": "VALID_TOKEN",
  "new_password": "NewPassword@123"
}

There was:

  • No nonce
  • No token invalidation
  • No rate limit
  • No binding between user and token

Once you had one valid token, you could reset any account by changing the email.

That alone is critical.

But the app still wasn't done.

🧊 The Cache That Shouldn't Exist

While replaying requests, I noticed this header:

X-Cache: HIT

On a password reset endpoint.

I had to recheck. Yes — the reset continuation endpoint was being cached.

After testing more carefully, I confirmed:

  • The cache key did not include user-specific headers
  • Reset responses containing tokens were cached
  • Cached responses were served to other users

That's when this turned from "bad logic" into mass exploitation.

💥 Cache Poisoning Meets Password Reset

By manipulating unkeyed headers (like X-Forwarded-Host) and forcing my reset response into cache, I could poison the response.

Other users requesting the same endpoint would receive my valid reset token.

No phishing. No interaction. No inbox access.

Just passive cache abuse.

This wasn't a single bug anymore — it was a chain:

  • Reset token reuse
  • Missing user binding
  • No invalidation
  • Sensitive response caching
  • Cache key misconfiguration

Each issue alone was bad. Together, they were devastating.

This allowed:

  • Full account takeover
  • Any user
  • At scale
  • Without user interaction
  • Using standard requests

This wasn't "reset your own password twice." This was "reset anyone's password if you understand the flow."

Password reset flows are stateful, no matter how REST-like they look.

Connect with Me!

#EnnamPolVazhlkai😇

#BugBounty, #CyberSecurity, #InfoSec, #Hacking, #WebSecurity, #CTF.