Authentication & Session Attacks
Credential stuffing, weak resets, JWT flaws, session fixation, and OAuth pitfalls.
Authentication is the gate. Break it and every other control - authorization, audit logs, data isolation - becomes meaningless, because you are now the user. This lesson covers the full landscape of authentication and session attacks: stuffing stolen credentials into login forms at scale, exploiting weak password-reset flows, hijacking sessions that the server hands you before you even log in, abusing JSON Web Token design flaws, and the identity-federation misconfigurations that turn a "Sign in with Google" button into an account takeover.
Test Only Accounts You Own
Credential stuffing and brute force against real systems is illegal. Password reset abuse, session manipulation, and JWT attacks on live production accounts you do not own constitutes unauthorized access. All techniques in this lesson must be practiced only in CTF environments, deliberately vulnerable apps (DVWA, Juice Shop, HackTheBox, TryHackMe), or on targets where you have explicit written authorization.
Credential Stuffing and Brute Force
Credential Stuffing
Billions of username:password pairs have leaked from breaches of LinkedIn, Adobe, RockYou, and hundreds of other platforms. A huge fraction of users reuse passwords. Credential stuffing automates the process of testing those leaked pairs against a new target.
The attacker does not need to crack anything. They take a leaked list and automate login attempts:
What attackers do with access: account takeover, reselling access, initiating fraudulent transactions, pivoting from a corporate email to internal SSO.
Detection signals: high login failure rate from diverse IPs (botnets), logins from unexpected geolocations, logins at unusual hours, a spike in "forgot password" requests after a public breach.
Brute Force
Brute force tries passwords systematically or from a wordlist. Unlike credential stuffing, there is no known password - just guessing.
Effective wordlists for targeted attacks:
- rockyou.txt - 14 million real passwords from the RockYou breach, included in Kali at
/usr/share/wordlists/rockyou.txt.gz - SecLists - curated password lists organized by use case
- Custom OSINT wordlist: combine target's name, company, birthdate, pet names scraped from social media (CeWL, Maltego)
Account Lockout Bypass
Many apps lock after N failed attempts per account but do not lock per-IP. Reverse brute force - one very common password (e.g., "Summer2024!") tried against many accounts - avoids per-account lockout. Distributed attacks using botnet IPs avoid per-IP rate limiting.
Weak Password Reset Flows
Password reset is often the weakest link in the authentication chain. Common flaws:
Predictable Reset Tokens
Some applications generate reset tokens using weak entropy:
# VULNERABLE: token based on timestamp + username
import hashlib, time
token = hashlib.md5(f"{username}{time.time()}".encode()).hexdigest()An attacker who knows the approximate time of the reset request can enumerate token values. The fix is 128+ bits of cryptographically random entropy:
import secrets
token = secrets.token_urlsafe(32) # 256 bits of randomHost Header Injection in Reset Emails
If the application builds the reset link using the Host header:
reset_url = f"https://{request.headers['Host']}/reset?token={token}"An attacker can intercept a victim's request (or trick them into visiting a page that triggers a reset), spoof the Host header to attacker.com, and receive the token:
POST /forgot-password
Host: attacker.com
Content-Type: application/json
{"email": "victim@example.com"}The victim receives an email containing https://attacker.com/reset?token=VALID_TOKEN. They click it; the attacker captures the token.
Fix: use the application's configured base URL from a server-side constant, never from the Host header.
Short-Lived but Reusable Tokens
Tokens that expire but are not invalidated after first use allow replay attacks. Any token should be single-use: invalidate it immediately upon redemption.
Username Enumeration via Timing
POST /forgot-password {"email": "existing@user.com"}
→ Response in 450ms (performs database lookup + email send)
POST /forgot-password {"email": "nonexistent@user.com"}
→ Response in 2ms (short-circuits at DB lookup)This timing side-channel reveals valid accounts. Fix with constant-time responses (even for non-existent users, simulate the full code path or use time.sleep to equalize timing).
Session Fixation
Session fixation attacks trick the server into using a session ID that the attacker already knows.
The attack flow:
- Attacker visits the application and receives a session ID:
SESS=abc123. - Attacker crafts a URL with that session ID embedded:
https://target.com/login?PHPSESSID=abc123. - Victim clicks the link, authenticates, and the server reuses the pre-authentication session ID (
abc123) for the now-authenticated session. - Attacker, who already knows
abc123, is now effectively logged in as the victim.
The fix is simple: regenerate the session ID after every privilege change, especially after login:
# Flask - regenerate session on login
from flask import session
session.clear()
# ... validate credentials ...
session['user_id'] = user.id # New session ID generated automaticallyIn PHP: session_regenerate_id(true) after a successful login. In Express.js: req.session.regenerate().
JWT Flaws
JSON Web Tokens (JWTs) are widely used for stateless authentication. They consist of three base64url-encoded sections: header, payload, and signature, joined by dots.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiJ1c2VyXzEwNDIiLCJyb2xlIjoidXNlciJ9
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cDecoded header: {"alg":"HS256","typ":"JWT"}
Decoded payload: {"sub":"user_1042","role":"user"}
The alg=none Attack
The JWT specification allows "alg":"none" to indicate an unsigned token. Many early JWT libraries honored this blindly:
- Decode the token.
- Modify the payload: change
"role":"user"to"role":"admin". - Set the header to
{"alg":"none","typ":"JWT"}. - Re-encode header and payload, omit the signature (or leave it empty).
- Send the crafted token.
If the server accepts it, you have arbitrary JWT claims.
Modern JWT libraries reject alg=none by default, but older or misconfigured instances are still found in the wild.
Weak HMAC Secret
HS256 signs the token with a shared secret. If the secret is weak (a short string, a default value like secret or password, or the application name), it can be cracked offline:
Once you have the secret, you can sign any payload you want - arbitrary claims, including "role":"admin".
Algorithm Confusion (RS256 to HS256)
Some systems support both RS256 (asymmetric, RSA) and HS256 (symmetric, HMAC). If the server uses RS256, it signs with a private key and verifies with the public key. The public key is, by definition, public.
Attack: obtain the public key (often retrievable from /jwks.json or the TLS certificate). Take an RS256-signed token, change the algorithm to HS256, and sign it with the public key as the HMAC secret. If the library's verification logic is naive:
"alg is HS256, I will verify HMAC with the configured 'key'"And if the library uses the same key for both algorithms, it will verify the forged HS256 token using the public key as the HMAC secret - which you supplied as the signing key.
JWT Security Checklist
- Explicitly configure the allowed algorithm(s) - never accept
alg=none. - Use a cryptographically random secret of at least 256 bits for HS256.
- Prefer RS256/ES256 (asymmetric) for distributed systems.
- Validate
exp(expiration),iss(issuer), andaud(audience) claims. - Keep JWT expiry short (15 minutes) and use refresh tokens.
OAuth and SSO Pitfalls
OAuth 2.0 and OpenID Connect power "Sign in with X" flows. When misconfigured, they allow account takeover without knowing the victim's password.
Open Redirect in redirect_uri
The OAuth flow sends users to a redirect_uri after authorization. If the provider does not validate this URI strictly:
https://oauth-provider.com/authorize
?client_id=MYAPP
&redirect_uri=https://attacker.com/callback
&response_type=code
&scope=openid+emailThe provider redirects the victim's authorization code to attacker.com. The attacker exchanges the code for tokens and is now logged in as the victim.
Fix: OAuth providers must validate redirect_uri against a pre-registered exact-match list. No wildcards, no substring matching.
State Parameter Missing (CSRF on OAuth)
The state parameter is a random nonce included in the authorization request and verified when the callback arrives. Without it, an attacker can trick a logged-in victim into completing an OAuth flow that binds the attacker's external account to the victim's app account.
Implicit Flow Token Leakage
The OAuth implicit flow (now deprecated) returns access tokens directly in the URL fragment. These tokens appear in browser history, referrer headers, and server logs of any third-party resources loaded on the redirect page.
Account Pre-Hijacking via Email Claim
Consider an app that creates an account when a new OAuth identity arrives, keyed by email. If the app also allows password-based registration:
- Attacker registers with password:
victim@gmail.com+ any password. - Account exists but email is unverified (no confirmation required, or attacker can confirm).
- Victim later signs up with "Sign in with Google" using
victim@gmail.com. - The app links the OAuth identity to the existing (attacker-controlled) account.
- Attacker logs in with their password and now has access to the victim's OAuth-linked data.
Fix: enforce email verification before linking OAuth identities, and treat provider-verified emails differently from user-supplied emails.
Seen in the wild · Reddit
Real HackerOne breakdown
A researcher discovered that Reddit's "Sign in with Apple" integration did not properly validate the email claim returned by Apple against existing Reddit accounts. When Apple relays a user's email (or a relay address), Reddit would link the Apple identity to any existing account sharing that email without additional verification. This allowed an attacker who could predict or enumerate a target's Apple relay email to pre-register a Reddit account and then have it linked when the victim used "Sign in with Apple" for the first time. Reddit patched the identity-linking logic to enforce strict email verification and confirmed-ownership checks before merging OAuth identities with existing accounts.
Prevention Checklist
Password Policies and Storage
- Enforce minimum length (12+ chars); check against known-breached lists (HaveIBeenPwned API).
- Hash passwords with bcrypt (cost factor 12+), Argon2id, or scrypt - never MD5, SHA-1, or unsalted SHA-256.
- Implement account lockout with exponential backoff, or better: CAPTCHA challenges after N failures.
Session Management
- Regenerate session ID after login and privilege changes.
- Set cookies:
HttpOnly; Secure; SameSite=Strict. - Enforce session expiry (idle timeout + absolute expiry).
- Invalidate server-side session records on logout - do not just delete the cookie.
MFA
Multi-factor authentication (TOTP, WebAuthn/passkeys) is the single most effective control against credential stuffing and brute force. Even a weak password with TOTP is dramatically harder to abuse at scale.
Monitoring
Alert on: multiple failed logins for one account, one IP hitting many accounts, successful logins from new geolocations, impossible travel (login from Paris followed by login from Tokyo 10 minutes later).
Key Takeaways
- Credential stuffing uses real leaked passwords to test logins at scale; brute force iterates guesses. Both require rate limiting, lockout, and MFA to be effective defenses.
- Password reset flows fail due to weak tokens, host header injection, and non-invalidation after use.
- Session fixation is prevented by regenerating session IDs after every login.
- JWT attacks include
alg=none, weak HMAC secrets, and RS256-to-HS256 algorithm confusion. - OAuth/SSO flaws cluster around unvalidated
redirect_uri, missingstateparameter CSRF protection, and insecure account-linking logic. - MFA is the highest-ROI single control against authentication attacks.