Account enumeration is when an application responds differently for a valid username than for an invalid one, letting an attacker build a list of real accounts. Scanners spot it by sending both kinds of attempt and comparing the responses. The fix is a generic message and uniform timing.
What it looks like
The OWASP Web Security Testing Guide describes the test: collect valid usernames by interacting with the authentication mechanism. Typical leaks:
- A message that says the password is wrong for one user and that the user is not recognized for another.
- Different HTTP status codes, response lengths or page titles.
- Different redirect URLs or error codes in the address bar, for example one error code for a bad user and another for a good user with a bad password.
- Folder style URLs where an existing user returns a 403 and a missing one returns a 404.
- A registration form that says an email is already in use.
The guide also notes that some applications do not treat usernames as sensitive and expose them on purpose. Understand the requirements before filing a finding.
Why it matters
Enumeration is rarely the final goal. It makes other attacks cheaper. With a confirmed list of valid users, an attacker can focus password guessing, credential stuffing or phishing on accounts that exist. It also reveals who uses your service, which can be private information in its own right.
How scanners find it
A scanner does what the testing guide describes by hand.
- Submit a valid username with a wrong password and record the full response.
- Submit a username that does not exist and record the response.
- Compare status, body, length, headers, redirects and sometimes timing.
- Repeat on the login form, the password reset form and the registration form.
If the two responses differ in a stable way, it reports enumeration. The tool needs a known valid account to do this, so providing test credentials improves the result.
What it can miss: subtle timing differences when the server does extra work for real users, such as hashing a password only if the account exists. The OWASP Authentication Cheat Sheet specifically warns that business logic can create a processing time difference that allows a time-based attack. Response size comparisons also fail when pages contain random tokens that change each time.
The fix
The Authentication Cheat Sheet says that for login, password reset and recovery, an application must return a generic error regardless of whether the user ID or password was wrong, the account does not exist, or the account is locked or disabled. Registration should follow the same idea. The Forgot Password Cheat Sheet adds two points: return a consistent message for existing and non-existing accounts, and make the response time uniform. It also says to send the reset method over a side channel such as email.
| Place | Safer behavior |
|---|---|
| Login | One message for any failure |
| Password reset | Same message and timing for known and unknown accounts |
| Registration | Do not reveal that an email already has an account, or send a notice to that email instead |
| Error codes and URLs | Same code and URL for every failure |
Pair this with lockout or throttling so the generic message is not the only barrier. See account lockout or throttling.
Try it yourself
Click through it at your own pace, or open it full screen. Look for how findings are grouped by the page and request that produced them.
Quick check
Is it acceptable to say "email not found" on password reset?
OWASP guidance says to return a consistent message for existing and non-existing accounts. Telling the user the email was not found breaks that.
Do generic messages hurt usability?
A little. Most teams accept that cost on login and reset, and offer clear help text such as "check your email" or "check your details" without confirming anything.
For the dependency side of your login stack, Safeguard has a free scan at gold.safeguard.sh/scan.
Sources
- OWASP WSTG: Account Enumeration and Guessable User Account
- OWASP Authentication Cheat Sheet
- OWASP Forgot Password Cheat Sheet
Last reviewed: 19 October 2026.