The lab
here is how portswigger describes it
This lab is vulnerable to username enumeration using its response times. To solve the lab, enumerate a valid username, brute-force this user's password, then access their account page.
the last enumeration lab leaked valid usernames through a missing full stop in the text. this one leaks them through time, the server takes a little longer to answer when the account is real, and that delay becomes the oracle we read.
why response time gives the answer away?#
sometimes the login response looks byte for byte identical whether the username exists or not, so there is nothing to grep or filter on. but the server still has to do different work in the two cases, and work takes time.
when a username does not exist, the app bails out early, there is no stored password to check against, so it answers almost instantly. when the username is real, it goes on to hash the password you supplied and compare it, and hashing is deliberately slow. so if we send a very long password, a valid username drags the response out noticeably while an invalid one stays quick, because only the valid account bothers to hash our giant string. the delay is the tell. two obstacles stand in the way though. the lab blocks your ip after too many tries, and timing needs a bit of care to read cleanly.
Step 1 - Get past the IP block with X-Forwarded-For#
we submit a junk login and send the POST /login request to repeater to experiment. before long we trip the lab's brute force protection, which blocks our ip after too many failed attempts.
the way around it is a header the app trusts, X-Forwarded-For. this header is meant to tell a server the original client ip when a request has passed through a proxy, and this app reads it to decide who to rate limit. since it is just a header we control, we can put a different value in it on every request and the app thinks each attempt is coming from a fresh ip, so the block never catches us.

Step 2 - confirm the timing difference#
now we experiment with the header in place and watch the response times rather than the bodies. feeding invalid usernames, the replies all come back in roughly the same quick time.

then we try our own real username with a long password and the response visibly lags behind. that confirms the mechanism, a valid username triggers the slow password hash while an invalid one does not, so a long password turns that work into a measurable delay.
Step 3 - Enumerate the username with a pitchfork attack#
to test the whole username list while still dodging the ip block, we send the request to intruder and choose the pitchfork attack type, which pairs up two payload sets and advances them together.
we add two insertion points, one on X-Forwarded-For and one on the username, and we set the password to a long string of about a hundred characters so that any valid username is forced into a slow hash. position one, the header, gets the numbers payload type set to the range 1 to 100 with a step of 1 and no fraction digits, which simply feeds a different fake ip each request. position two gets the username wordlist.

we run it and sort by response time. one username stands out with a response far slower than the rest. we replay it a few times to be sure the delay is consistent and not just network noise, and once it holds up we note that username as the valid one.

Step 4 - brute force the password and solve#
now we set up a fresh pitchfork attack for the password phase. again we keep a payload position on X-Forwarded-For to stay unblocked, fix the username to the valid one we just found, and put a payload position on the password.
position one gets the list of numbers again to keep spoofing the ip, and position two gets the password wordlist. then we start the attack.

when it finishes we look for the odd response out, and this time the tell is the status code. one password returns a 302 redirect instead of the usual failed login, which means that login succeeded and the server is forwarding us into the account.

we log in with the username and password we uncovered and reach the account page.
with this, the lab is solved!
