⚙️ 01. — Username and Password Enumeration via Different Responses

medium.com · The4v1 · 2 days ago · research
quality 7/10 · good
0 net
⚙️ 01. — Username and Password Enumeration via Different Responses | by The4v1 - Freedium Milestone: 20GB Reached We’ve reached 20GB of stored data — thank you for helping us grow! Patreon Ko-fi Liberapay Close < Go to the original ⚙️ 01. — Username and Password Enumeration via Different Responses Difficulty: 🟢 Apprentice The4v1 Follow ~9 min read · April 7, 2026 (Updated: April 7, 2026) · Free: Yes Goal: 🔍 Go to the login page — submit any fake credentials — capture POST /login in Burp Proxy — send to Intruder — set username as the payload position — load the candidate usernames wordlist — run a Sniper attack 🔎 Sort results by Length — find the one response that is longer than all others — check its response body — it says Incorrect password instead of Invalid username — that is the valid username 🔬 Clear positions — fix username to the discovered value — set password as the new payload position — load the candidate passwords wordlist — run a second Sniper attack 💡 Sort results by Status — find the one response with 302 instead of 200 — that is the correct password 🎉 Log in with the discovered username and password → access the account page → lab solved! 🧠 Concept Recap Username enumeration via different responses exploits the fact that the login form returns different error messages depending on whether a username exists or not. Invalid username means the username does not exist. Incorrect password means the username exists but the password is wrong. This difference is enough to enumerate valid usernames systematically. Combined with no rate limiting, an attacker can brute-force the full credentials in two Intruder passes — first enumerate the username, then brute-force the password. 📊 The Two Attack Phases 🔵 Phase 1 — Enumerate Username → Target: username parameter → Wordlist: Candidate usernames → Success Signal: Response length is longer / says "Incorrect password" 🔴 Phase 2 — Brute-force Password → Target: password parameter → Wordlist: Candidate passwords → Success Signal: Status code 302 (redirect to /my-account) The full attack flow: Phase 1: Username enumeration POST /login with username=§WORDLIST§&password=anything → Most responses: "Invalid username" (same length) → One response: "Incorrect password" (different length = valid username) Phase 2: Password brute-force POST /login with username=FOUND_USER&password=§WORDLIST§ → Most responses: HTTP 200 "Incorrect password" → One response: HTTP 302 redirect to /my-account = correct password ✅ Phase 3: Login Enter discovered username + password → account page → lab solved 🛠️ Step-by-Step Attack 🔧 Step 1 — Capture the Login Request 🌐 Click "Access the lab" 🔌 Ensure Burp Suite is running and proxying traffic 🖱️ Go to the login page → enter any fake credentials: username: test password: test 4. 🖱️ Click Submit 5. 🖱️ In Burp → Proxy → HTTP history → find POST /login Request body: username=test&password=test Response body: "Invalid username" → This is the error message for non-existent usernames → We will use the change in this message to detect valid usernames 6. 🖱️ Right-click POST /login → "Send to Intruder" 🎯 Phase 1 — Username Enumeration Step 2 — Configure Intruder for Username Attack 🖱️ Go to Intruder tab → Positions sub-tab 🖱️ Click "Clear §" to remove any auto-detected positions ✏️ Highlight the value test after username= → click "Add §" Positions should look like: username=§test§&password=test Attack type: Sniper (default - one payload position, one wordlist) The password value stays as "test" - it doesn't matter for Phase 1. We only need to identify which usernames exist, not validate passwords. Step 3 — Load the Username Wordlist 🖱️ Go to Payloads sub-tab 🖱️ Payload type → Simple list 🖱️ Click "Paste" or "Load" → paste the candidate usernames from: https://portswigger.net/web-security/authentication/auth-lab-usernames The wordlist contains common usernames like: carlos, root, admin, test, guest, info, adm, mysql, user, administrator... (around 100 entries total) 4. 🖱️ Click "Start attack" — a new results window opens Step 4 — Identify the Valid Username 🖱️ Wait for the attack to finish 🖱️ Click the "Length" column header to sort by response length What you see: → Most rows: same length (e.g., 3248 bytes) — all say "Invalid username" → One row: DIFFERENT length (e.g., 3261 bytes) — slightly longer Why the length differs: → "Invalid username" = 16 characters → "Incorrect password" = 18 characters → The valid-username response has 2 extra characters → longer overall length → This length difference is the signal 3. 🖱️ Click the outlier row → open Response tab → confirm the body says: Incorrect password 4. 🖱️ Note the Payload column value — this is your valid username Example: the valid username might be "atlanta" or "adm" etc. Write it down — you need it for Phase 2. 💣 Phase 2 — Password Brute-Force Step 5 — Reconfigure Intruder for Password Attack 🖱️ Close the attack results window → go back to Intruder → Positions 🖱️ Click "Clear §" to remove all current positions ✏️ Replace test in username=test with your discovered username ✏️ Highlight the value test after password= → click "Add §" Positions should now look like: username=VALID-USERNAME&password=§test§ The username is now FIXED to the valid one you found. Only the password is being tested. Step 6 — Load the Password Wordlist 🖱️ Go to Payloads sub-tab 🖱️ Click "Clear" to remove the username list 🖱️ Click "Paste" or "Load" → paste the candidate passwords from - https://portswigger.net/web-security/authentication/auth-lab-passwords The wordlist contains common passwords like: 123456, password, 12345678, qwerty, 123456789, 12345, 1234, 111111... (around 100 entries total) 4. 🖱️ Click "Start attack" Step 7 — Find the Correct Password 🖱️ Wait for the attack to finish 🖱️ Click the "Status" column header to sort by HTTP status code What you see: → Most rows: HTTP 200 — wrong password, page reloads with error → One row: HTTP 302 — redirect to /my-account = LOGIN SUCCESS ✅ Why 302: → Successful login → server redirects user to their account page → Failed login → server returns 200 with the login page + error message → 302 is unambiguous - there is exactly one correct password 3. 🖱️ Click the 302 row → check Response tab → confirm Location: /my-account 4. 🖱️ Note the Payload column value — this is the correct password 🏁 Step 8 — Log In and Solve the Lab 🌐 Go to the login page in your browser ✏️ Enter the discovered username and password 🖱️ Click Submit You are redirected to /my-account → lab solved! ✅ ┌──────────────────────────────────────────────────────────────────┐ │ ✅ Congratulations! │ │ │ │ You exploited username enumeration via different responses: │ │ Intruder Sniper on username → "Incorrect password" outlier → │ │ Found valid username → │ │ Intruder Sniper on password → HTTP 302 outlier → │ │ Found correct password → logged in → account page accessed. │ │ │ │ Lab: Username enumeration via different responses │ │ Status: SOLVED ✓ │ └──────────────────────────────────────────────────────────────────┘ 🔗 Complete Attack Chain Step 1: Login page → submit fake credentials → POST /login captured Send to Intruder ↓ Phase 1: Intruder → Positions → username=§test§&password=test Payloads → candidate usernames wordlist → Start attack Sort by Length → find outlier row → Response says "Incorrect password" Note the valid username from Payload column ↓ Phase 2: Intruder → Positions → username=VALID-USER&password=§test§ Payloads → clear list → candidate passwords wordlist → Start attack Sort by Status → find HTTP 302 row Note the correct password from Payload column ↓ Step 8: Login page → enter username + password → Submit Redirected to /my-account → Lab solved! 🎉 ⚙️ Deep Understanding — Every Component Explained Why Different Error Messages Enable Enumeration Secure login form (generic message): Wrong username → "Invalid username or password" Wrong password → "Invalid username or password" → Attacker cannot distinguish between the two cases → Cannot enumerate usernames without brute-forcing both simultaneously Vulnerable login form (this lab): Wrong username → "Invalid username" Wrong password → "Incorrect password" → Attacker knows EXACTLY which case they're in → Can enumerate usernames with 100% certainty → Reduces work: enumerate usernames first, then brute-force only passwords Even subtle differences are exploitable: → Different response lengths (even 1 byte difference) → Different response times (valid username takes longer to hash password) → Different redirect targets → Different HTTP status codes → Any observable difference in the response Why Sort by Length (Phase 1) and Status (Phase 2) Phase 1 — Sort by Length: "Invalid username" = 16 chars → shorter response body "Incorrect password" = 18 chars → longer response body → The valid username response is slightly larger overall → Length sort makes the outlier immediately visible as one row Alternative: sort by Response and look for "Incorrect password" text Or: use Grep Match in Intruder Options → add "Incorrect password" → Matching rows get a tick → easy to spot Phase 2 - Sort by Status: Wrong password → HTTP 200 (page reloads with error message) Correct password → HTTP 302 (redirect to /my-account) → Status sort groups all 200s together and the one 302 is obvious → No ambiguity - there is exactly one correct password in the list Alternative: sort by Length (302 redirect has shorter body than 200 error page) Or: use Grep Match → add "Location: /my-account" Sniper Attack Type — Why It's the Right Choice Burp Intruder attack types: Sniper: → One payload list, one position at a time → Tests each payload in the list against ONE position → Total requests = length of wordlist → Used here: one pass for usernames, one pass for passwords Cluster Bomb: → Multiple payload lists, multiple positions → Tests EVERY COMBINATION of all lists → Total requests = list1 size × list2 size → For 100 usernames × 100 passwords = 10,000 requests → Takes much longer, but can be done in one pass Why Sniper is better here: → We can enumerate the username first (100 requests) → Then brute-force only that user's password (100 requests) → Total: 200 requests instead of 10,000 → 50x faster, same result → This is the "enumerate first" strategy the lab notes mention 🚀 Troubleshooting Issue: All responses have the same length — can't find valid username → Don't just look at length — also check the Response tab for "Incorrect password" → Use Intruder → Options → Grep Match → add "Incorrect password" → Matching responses get a tick in a new column — easy to spot → Make sure you loaded the CORRECT wordlist (candidate usernames, not passwords) Issue: No HTTP 302 in Phase 2 results → Check you set the username to the CORRECT valid username from Phase 1 → Make sure the password position is marked: password=§test§ not username → Confirm you loaded the candidate PASSWORDS wordlist (not usernames again) → Try sorting by Length instead - the 302 response is shorter than 200 responses Issue: Intruder is very slow (Community Edition throttling) → Burp Community throttles Intruder to ~1 request/second → Phase 1 (100 usernames) = ~100 seconds = ~1.5 minutes - just wait → Phase 2 (100 passwords) = ~100 seconds - wait again → Burp Pro: no throttling - attacks complete in seconds Issue: "Clear §" removed my username too - positions look wrong → After clicking Clear §, manually type or paste: username=VALID-USERNAME&password=§test§ → Or: highlight only the "test" after password= then click Add § → Double check - only ONE § pair should exist and it must wrap the password value 🛡️ How to Fix This Vulnerability Fix 1: Always Use Generic Error Messages # ❌ VULNERABLE — reveals which part is wrong: if user not found: return "Invalid username" if password wrong: return "Incorrect password" # ✅ SECURE - same message regardless: if login failed (for ANY reason): return "Invalid username or password" # → Attacker cannot distinguish between wrong username and wrong password Fix 2: Implement Rate Limiting and Account Lockout # ✅ SECURE — lock account after failed attempts: MAX_ATTEMPTS = 5 if failed_attempts[username] >= MAX_ATTEMPTS: lock_account(username, duration=15_minutes) return "Account temporarily locked" # ✅ Also: rate-limit by IP address: if requests_per_minute[ip] > 10: return HTTP 429 Too Many Requests Fix 3: Use Constant-Time Comparison # ❌ VULNERABLE — timing difference reveals valid usernames: if username not in database: return error immediately ← fast response if password wrong: return error after hashing ← slow response (bcrypt takes time) # ✅ SECURE - same processing time regardless: import hmac dummy_hash = "$2b$12$dummyhashforinvalidusernames" stored_hash = get_hash(username) or dummy_hash # always do the hash if not hmac.compare_digest(bcrypt_check(password, stored_hash), True): return "Invalid username or password" # same delay always Fix Summary Priority 1 — Generic error messages: ✅ "Invalid username or password" — never reveal which part failed → Eliminates the information leakage that enables enumeration Priority 2 - Rate limiting and account lockout: ✅ Lock after 5 failed attempts - exponential backoff ✅ IP-based rate limiting - block suspicious IPs → Makes brute-force attacks impractical even if enumeration works Priority 3 - Constant-time authentication: ✅ Always run the password hash even for invalid usernames → Prevents timing-based enumeration attacks Priority 4 - CAPTCHA after failed attempts: ✅ Require CAPTCHA after 3+ failures - stops automated tools → Burp Intruder cannot solve CAPTCHAs automatically Priority 5 - Multi-factor authentication: ✅ Even with correct credentials - attacker needs 2nd factor → Makes credential brute-force attacks irrelevant 👏 If this helped you — clap it up (you can clap up to 50 times!) 🔔 Follow for more writeups — dropping soon 🔗 Share with your pentest team 💬 Drop a comment #bug-bounty #portswigger #web-security #cybersecurity Reporting a Problem Sometimes we have problems displaying some Medium posts. If you have a problem that some images aren't loading - try using VPN. Probably you have problem with access to Medium CDN (or fucking Cloudflare's bot detection algorithms are blocking you).