User Enumeration
Attacker harvests a verified list of customer emails by reading different error bodies and response times from the login endpoint, then ship the unified error and constant-time stand-in.
What Is User Enumeration?
User enumeration is the application telling an attacker which emails or usernames exist. The hostile primitive is any difference in observable behaviour between the registered-user path and the unregistered-user path: a different error string, a different status code, a different response time, a different downstream side-effect. Once that signal exists, an attacker scripts it and walks a wordlist of candidate addresses, keeping only the registered subset. That subset is gold, it feeds credential stuffing, targeted phishing, and password-reset abuse, and it stays under most rate-limit thresholds because each probe is a single attempt that returns a clean 401. This exercise puts you on both sides. As Bob, preparing a credential-stuffing campaign, you send two probe requests to Larksong's sign-in endpoint: one with a definitely-unknown email, one with an email you believe is registered. The body changes ('No account found' vs 'Incorrect password') and the latency changes (bcrypt only runs when the user exists). Either channel is enough. As Alice, on the receiving end of a Security Operations enumeration alert, you open the handler, see the two-branch control flow, and apply the fix: a single check that returns one generic error body and always runs the bcrypt comparison, against a fixed dummy hash when no user exists, so both the error-body and the latency oracle are closed. The exercise closes with quiz questions on what enumeration is, why timing matters even after the body is unified, and where else the same shape appears (signup, password reset).
What You'll Learn in User Enumeration
- Recognize that any difference between the registered and unregistered code paths (error body, status code, latency, side effect) is an enumeration oracle
- Apply the unified-error fix on login and ensure the no-user path runs the same bcrypt cost as the user-exists path
- Understand that timing is a second enumeration channel that survives the body fix and must be closed with a constant-time stand-in
- Audit signup and password-reset endpoints for the same shape, both routinely distinguish registered from unregistered in their happy-path responses
- Connect enumeration to credential stuffing: filtering the candidate list to registered emails turns a noisy spray into a quiet, rate-limit-friendly campaign
User Enumeration — Training Steps
-
A list that needs a filter
Bob is holding a dump of email and password pairs from an unrelated breach. Most of those people have never heard of Larksong, so spraying the whole list at the sign-in page would be slow and noisy. First he wants to know which of those emails are actually Larksong accounts. He opens the Larksong sign-in page in his browser, with DevTools ready, to watch how it responds to a failed login.
-
Try an email that isn't registered
Bob starts with a baseline. He signs in with an address that almost certainly has no Larksong account and a throwaway password, and watches the response land in the Network panel.
-
The baseline answer
The form shows a sign-in error, and the real detail is in the Network panel: a 401 with a response body and a response time. This is what a failed login against an unregistered email looks like. Bob notes it as his baseline.
-
Try an email he thinks is real
Now Bob retries with an address from his dump that he believes belongs to a real listener, marcus.delgado@gmail.com, still with a throwaway password. If the endpoint treats the two cases the same, the response will match the baseline. If it does not, the difference is the whole prize.
-
A different answer
The answer changed. Same wrong password, but the response body is different. Bob never logged in, yet the sign-in endpoint just told him this email is a real Larksong account.
-
Check the response times
The wording is one tell. The Network panel holds a second, quieter one: how long each request took.
-
Knowledge check
Before Bob scales this up, be precise about why the real account was so much slower.
-
Harvest the list
Bob points a script at the sign-in endpoint and feeds it every email from his dump, one request each. Whenever the answer is the slow Incorrect password , that address goes on his confirmed list. Out of 50,000 candidates, 8,400 are real Larksong accounts. Now the credential-stuffing run that follows only targets addresses he knows exist, so it stays small, quiet, and well under the rate limits that would flag a blind spray.
-
The security alert
You own Larksong's sign-in service. Overnight, detection flagged a strange pattern against the login endpoint and emailed you the finding. Read what it caught, then open the code.
-
Open the login handler
The login route lives in the auth service. Open the handler and look at how it decides what to send back when a sign-in fails.