This is the first actual lab walkthrough in the series. The lab is from PortSwigger's Authentication category , the first one on the list and it's a clean introduction to two things you'll run into constantly: username enumeration and password brute-forcing.
The setup The lab gives you a login form and two wordlists , one for usernames, one for passwords. The goal is simple on paper:
- Find a valid username
- Brute-force its password
- Log in
What makes it solvable is that the app responds differently depending on whether the username exists. That's the whole bug. A login form should never tell you which half of "username + password" was wrong , it should give one generic error and move on. This one doesn't.
How to spot username enumeration in general Before jumping into the steps, this is worth saying because it applies to every lab in this category (and to real targets too). When you're trying to figure out if a login form leaks valid usernames, these are the signals to watch:
- Status code differences — most attempts return the same status. If one returns something different (200 vs 302, for example), that's a strong signal the username is valid.
- Error message differences — "wrong username" vs "wrong password" instead of a single generic "invalid credentials" message. Sometimes it's subtle: a missing period, a different word order.
- Response time differences — if valid usernames trigger a password check and invalid ones return immediately, you can detect it by timing. Trick: submit a deliberately long password so the password check takes measurable time.
- Response length differences — even when the message looks the same in the browser, the raw response body can differ by a few bytes. Burp Intruder's Length column makes this obvious.
That last one is what cracks this specific lab.
Walkthrough Step 1 — Probe the login form manually. First thing I always do: try admin:admin. Not because I expect it to work, but because I want to see what the app says when something is wrong. It returned "Invalid username" , which is already the bug. The app just told me that admin is not a valid user. If it had said "Invalid credentials," I'd have no idea whether the username or password was the problem.
Step 2 — Send to Intruder. Turned on Burp's proxy, submitted the login again, intercepted the request, right-clicked → Send to Intruder.
Step 3 — Configure the username attack. In Intruder, I cleared the auto-set payload positions and marked only the username parameter. Attack type: Sniper (single payload set, single position , is exactly what we want here). Loaded the provided usernames wordlist as the payload, hit Start attack.
Step 4 — Find the valid username. Once the attack finished, I sorted results by Length. Almost every response had the same length, the standard "Invalid username" page. One row stood out with a different length. The response body for that one said "Incorrect password" instead of "Invalid username." That's the giveaway: the app got far enough to check the password, which means the username is valid. Valid username: hack it ;). Quick note : in this lab the response length was the cleanest signal, but I always check the response body too. The length alone tells you something is different; the body tells you what.
Step 5 — Configure the password attack. Sent a new login request to Intruder, this time with valid username and any password. Cleared the positions, marked only the password parameter. Loaded the passwords wordlist. Sniper attack again, Start.
Step 6 — Find the valid password. This time I sorted by Status code. Most responses came back as 200 , the login page re-rendering with an error. One came back as 302 , a redirect, which is what a successful login does (redirect you to your account page). Valid password: hack it ;)
Step 7 — Log in. Took username:password back to the login form, submitted. Logged in. Lab solved.
What a dev should do about this The fix isn't complicated, but it's easy to get wrong because the bad behavior often comes from someone trying to be helpful. Specific error messages feel user-friendly. They're not, they're an information leak. Things to actually do:
- Generic error message. One message for all auth failures: "Invalid username or password." No variations. No "user not found" anywhere in the auth flow.
- Consistent response timing. If the password check is what makes valid-username responses slower, run the password hash check even when the username doesn't exist (against a dummy hash). Equal work = equal time.
- Consistent response shape. Same status code, same body length where reasonable, same redirect behavior. Don't let a 302 leak success.
- Rate limiting and lockout. Even if everything above is done right, you don't want someone hammering your login at 1000 req/s. IP-based throttling, account lockout with backoff, CAPTCHA after N failures.
- Watch the registration and "forgot password" flows too. Same enumeration trick works there if you tell the user "this email is already registered" or "no account with that email."
Coming from a QA and dev background, this is the kind of bug I'd want to catch in a code review: any branch in auth logic that produces a user-visible difference is a leak. The mental check is : "if I swap a valid input for an invalid one, can an outside observer tell the difference?" If yes, you have enumeration.
Closing That's lab #1 done. Next one in this series will be Username enumeration via subtly different responses, same category, but the leak is hidden in a way that breaks the naive "sort by length" approach. Spoiler: grep matching becomes your friend.
If this helped, or if you'd have done something differently, let me know. ~ b4dk4rm4sec