Account Enumeration
debt(d5/e5/b5/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list semgrep and owasp-zap as tools that can detect differing error messages and response patterns. Semgrep can catch the obvious code pattern of returning different messages for 'user not found' vs 'wrong password'. OWASP ZAP can detect enumeration via response differences during dynamic scanning. However, subtler timing-based leaks require more careful analysis, pushing slightly beyond basic tooling but not to the level of 'only careful code review' (d7).
Closest to 'touches multiple files / significant refactor in one component' (e5). While the quick_fix describes normalizing response messages and always running password_verify(), a thorough fix requires addressing multiple endpoints: login, registration, password reset, and potentially API endpoints. Each must return identical timing and messages. The common_mistakes enumerate at least four distinct locations (login messages, timing, password reset, registration) that all need remediation, making this a multi-file fix across several authentication flows.
Closest to 'persistent productivity tax' (b5). Account enumeration defences apply across web and API contexts and touch every authentication-adjacent flow (login, registration, password reset, account recovery). Every new feature that interacts with user identity must consider enumeration leakage. This creates an ongoing tax on development — not quite system-defining (b7/b9), but it shapes how all auth-related code is written and reviewed, affecting many work streams persistently.
Closest to 'serious trap — contradicts how a similar concept works elsewhere' (t7). The misconception field explicitly states that developers believe a generic 'invalid credentials' message fully prevents enumeration, when in reality timing differences, CAPTCHA behaviour, and password-reset flows still leak account existence. This is a serious trap because the 'obvious' fix (unifying error messages) gives false confidence while leaving multiple other enumeration vectors open. Developers who think they've solved the problem are often still vulnerable through timing side-channels and auxiliary flows.
Also Known As
TL;DR
Explanation
Account enumeration happens when an application reveals whether a username exists through different HTTP status codes, response bodies, response times, or error messages (e.g., 'This email is not registered' vs. 'Incorrect password'). Armed with a valid username list, attackers can run targeted credential-stuffing or brute-force attacks. Prevention: use generic error messages ('Invalid credentials'), ensure consistent response times with a dummy password-check for unknown users, and implement rate limiting.
Diagram
sequenceDiagram
participant ATK as Attacker
participant APP as Application
ATK->>APP: POST /login user=alice@ex.com pass=wrong
APP-->>ATK: Email not found - reveals alice does NOT exist
ATK->>APP: POST /login user=bob@ex.com pass=wrong
APP-->>ATK: Wrong password - reveals bob DOES exist
Note over ATK,APP: Attacker now knows valid accounts
Note over ATK,APP: Fix: always return same message
ATK->>APP: POST /login user=bob@ex.com pass=wrong
APP-->>ATK: Invalid credentials - no account info leaked
Common Misconception
Why It Matters
Common Mistakes
- Returning 'user not found' vs 'wrong password' — both messages reveal account existence.
- Different response timing for valid vs invalid usernames leaks the same information as different messages.
- Password reset forms that say 'no account for that email' rather than a generic confirmation.
- Registration forms that reveal 'username already taken' without rate-limiting or CAPTCHA.
Avoid When
- Do not use distinct error messages per failure reason on auth endpoints — merge all failures into one generic response.
- Avoid varying response timing based on whether an account exists — constant-time comparisons prevent timing oracle attacks.
- Do not expose account existence through side-channels like redirect destinations or HTTP status codes.
When To Use
- Understanding this attack is essential when designing login, registration, and password-reset flows.
- Apply enumeration defences on any endpoint where attacker knowledge of a valid account provides value.
Code Examples
// Different messages reveal whether account exists
if (!$user) {
return 'No account found with that email';
}
if (!password_verify($pw, $user->password)) {
return 'Incorrect password'; // confirms account exists!
}
// Same message regardless of failure reason
function login(string $email, string $password): ?User {
$user = User::where('email', $email)->first();
// Always run password_verify — prevents timing-based enumeration too
$hash = $user?->password ?? '$argon2id$v=19$m=65536,t=4,p=1$dummy$dummy';
$valid = password_verify($password, $hash);
if (!$user || !$valid) {
return null; // identical response for both failure cases
}
return $user;
}