← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Account Enumeration

Security CWE-203 OWASP A2:2021 CVSS 5.3 PHP 5.0+ Intermediate
debt(d5/e5/b5/t7)
d5 Detectability Operational debt — how invisible misuse is to your safety net

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).

e5 Effort Remediation debt — work required to fix once spotted

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.

b5 Burden Structural debt — long-term weight of choosing wrong

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.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

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.

About DEBT scoring →

Also Known As

user enumeration username enumeration

TL;DR

Differing application responses to valid vs. invalid usernames allow attackers to build a list of registered accounts.

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

A generic "invalid credentials" message fully prevents enumeration. Timing differences, CAPTCHA behaviour, and password-reset flows can still reveal whether an account exists even when error messages are identical.

Why It Matters

Revealing which usernames exist lets attackers target brute-force and credential-stuffing attacks precisely, turning a failed login into a reconnaissance win.

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

💡 Note
The bad example returns "No account found with that email" — an attacker can confirm which emails are registered; the fix returns the same message for wrong email and wrong password.
✗ Vulnerable
// 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!
}
✓ Fixed
// 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;
}

Added 15 Mar 2026
Edited 31 Mar 2026
Views 65
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 1 ping W 1 ping T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 1 ping T 1 ping F 1 ping S 1 ping S 3 pings M 1 ping T 0 pings W 0 pings T 0 pings F 1 ping S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 1 ping T 0 pings W
No pings yet today
PetalBot 1
Amazonbot 16 Perplexity 10 Scrapy 6 Google 5 Ahrefs 5 ChatGPT 3 SEMrush 3 Majestic 2 Claude 2 Unknown AI 1 Meta AI 1 PetalBot 1
crawler 51 crawler_json 4
DEV INTEL Tools & Severity
🟡 Medium ⚙ Fix effort: Low
⚡ Quick Fix
Return identical response time and message for 'user not found' and 'wrong password' — use hash_equals() and always run password_verify() even for non-existent users
📦 Applies To
PHP 5.0+ web api
🔗 Prerequisites
🔍 Detection Hints
Login returning 'user not found' vs 'wrong password' separately; password reset revealing whether email exists
Auto-detectable: ✓ Yes semgrep owasp-zap
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: Medium Context: Function Tests: Update
CWE-203 CWE-204


✓ schema.org compliant