SecureBlockLog inStart a pentest
Vulnerability Repository
MediumAuthentication

User Enumeration

Differences in application responses reveal whether a username or email address exists, enabling attackers to build valid account lists for targeted credential attacks.

CVSS 5.3CWE CWE-204OWASP A07:2021 — Identification and Authentication Failures

Description

User enumeration is an information disclosure weakness where an application responds differently depending on whether a supplied username or email address exists in its database. CWE-204 — Observable Response Discrepancy covers this class of vulnerability, where the system's varying behaviour under different inputs leaks information about its internal state.

The vulnerability appears most commonly in login forms, password reset flows, registration endpoints, and account recovery mechanisms. Applications that display "Email not found" versus "Incorrect password" are the most straightforward example, but enumeration can also occur through subtle differences in HTTP status codes, response timing, response body length, or even the wording of CAPTCHA challenges.

Under A07:2021 — Identification and Authentication Failures, the OWASP Top 10 explicitly calls out enumerable account existence as a contributing factor to credential stuffing and brute-force attacks. A confirmed list of valid email addresses dramatically increases the success rate of these automated attacks.

How It Works

Distinct error messages — the most common form:

POST /forgot-password
{"email": "nonexistent@example.com"}
→ 200: "No account found with that email address."

POST /forgot-password
{"email": "real.user@example.com"}
→ 200: "If an account exists, a reset link has been sent."

The difference in response text confirms the second email is valid.

HTTP status code differences:

POST /api/login
{"username": "nonexistent", "password": "wrong"}
→ 404 Not Found

POST /api/login
{"username": "admin", "password": "wrong"}
→ 401 Unauthorized

A 404 versus 401 directly confirms account existence.

Timing-based enumeration — password comparison using bcrypt is computationally expensive. When a username does not exist, some applications skip the password hash comparison entirely, returning a response several hundred milliseconds faster. Tools like ffuf with timing analysis (-t 1 for single-threaded timing comparison) can detect sub-second differences across large wordlists.

Registration endpoint enumeration:

POST /register
{"email": "real.user@example.com"}
→ 422: "An account with this email already exists."

Impact

  • Account list construction — valid usernames and emails are compiled for use in credential stuffing campaigns using breach databases.
  • Targeted spear phishing — confirmed active accounts can be targeted with personalised phishing pretending to be legitimate account security notifications.
  • Brute-force efficiency — eliminating invalid usernames reduces brute-force keyspace by orders of magnitude, making attacks against rate-limited endpoints more feasible.
  • Account existence as sensitive information — on platforms where account existence is itself sensitive (e.g., dating apps, medical portals, financial services), enumeration is a standalone privacy violation.
  • Password spraying facilitation — a confirmed account list combined with common passwords (Password1!, Summer2024!) achieves account compromise at scale while evading per-account lockout policies.

Detection

  1. Compare login error responses — submit requests with a known-invalid username and a known-valid username (create a test account) with incorrect passwords. Compare response body, HTTP status code, and response time across 10+ requests to identify any consistent difference.
  2. Test password reset flows — submit reset requests for valid and invalid email addresses and compare the full HTTP response including headers, body length, and response time.
  3. Check registration endpoints — attempt to register with a known-existing email and compare the error message to that returned for a new email.
  4. Measure response timing — use ffuf or Burp Intruder with a large username list and sort results by response time. Flag any cluster of responses that are consistently faster or slower.
  5. Test account lockout discrepancy — verify that lockout mechanisms do not reveal account existence through different error messages post-lockout versus pre-lockout for non-existent users.

Remediation

Normalise all authentication responses. Return identical messages for "user not found" and "wrong password" scenarios. Use a single generic message such as "Incorrect username or password."

Implement constant-time comparisons. Always perform the full password hash comparison even when the account does not exist. Supply a dummy hash to bcrypt.compare() or equivalent for non-existent users.

# Always run bcrypt comparison, even for non-existent users
dummy_hash = "$2b$12$dummyhashfornonexistentusers..."
stored_hash = user.password_hash if user else dummy_hash
bcrypt.checkpw(password.encode(), stored_hash)

Apply consistent HTTP status codes. Return 401 for all failed authentication attempts regardless of whether the username exists.

Rate limit and implement CAPTCHA. Even with normalised responses, rate limiting at the IP and account level limits the throughput of automated enumeration.

Ready when you are
Scope a pentest in the next two minutes.
Start scoping