Finding
I spent four days for just hoping i got the vulnerability, i test the site with regular testing(like xss, sqli, scanning, prototype pollution, etc.) and brute force experiments.
During four days of testing I observed anomalous behavior, the website was extremely slow and unresponsive at certain times, and the other times it was perfectly fast.
Seeing those two behaviours, I ran login brute force tests during the slow periods and again during the fast periods.
- At normal times everything behaved, wrong passwords got 401s, right password got 200 and a token
- But at certain times of day the site would crawl. Not “a little slow” full-on lag when doing login or register.
and then i figured it was the server being overloaded.
So I tried brute force again during both t…
Finding
I spent four days for just hoping i got the vulnerability, i test the site with regular testing(like xss, sqli, scanning, prototype pollution, etc.) and brute force experiments.
During four days of testing I observed anomalous behavior, the website was extremely slow and unresponsive at certain times, and the other times it was perfectly fast.
Seeing those two behaviours, I ran login brute force tests during the slow periods and again during the fast periods.
- At normal times everything behaved, wrong passwords got 401s, right password got 200 and a token
- But at certain times of day the site would crawl. Not “a little slow” full-on lag when doing login or register.
and then i figured it was the server being overloaded.
So I tried brute force again during both the fast windows and the slow windows.
- During fast windows it was boring and expected: everything rejected except the correct password.
- During slow windows something weird happened. I found five different passwords that all returned 200 and gave me valid tokens, none of those passwords were the real password.
Press enter or click to view image in full size
At first I thought I was seeing things, but I validated the tokens against protected endpoints and they worked.
After finding out, it wasn’t magic. It was a TOCTOU race condition. In plain short the function that checks whether a login is allowed and the part that actually issues the token aren’t synchronized. If the auth function is slower than the incoming requests, multiple requests can slip past the check before the system updates state, so several different wrong passwords can all look “valid” because requests overtake the function’s internal timing.
What’s actually happening
Press enter or click to view image in full size
source: portswigger.net
Imagine the login flow as two streets:
- the “check” street
and
- the “use” street
The code first walks down the check street and says “cool, this account looks allowed right now,” then walks down the use street and says “okay, I’ll create a session/token.”
If these two steps happen back to back, everything works perfectly.
But if the check step is slow (heavy CPU, slow DB, blocked I/O, overloaded server), multiple incoming requests line up behind it. They all get told “cross” by the initial check before the token-creation step finishes. The result several requests reach the token-generation step even though the password validation should’ve failed.
In short: when the auth function is slower than the incoming request rate, requests can overtake internal state and produce inconsistent results.
**TL;DR: **Database latency created a race window in the login flow: the password check ran slower than incoming requests, letting multiple wrong passwords reach the token creation step. Result real tokens issued for invalid logins.