Login Attacks & Defences
Chapter 2 made stolen hashes hard to crack. This chapter defends the live login endpoint — where attackers don't need your database, they just guess against your form. The attacks differ in how they guess, and the defences differ accordingly, but they share a theme: make guessing slow, costly, and visible.
The Family of Guessing Attacks
| Attack | How it guesses | Targets |
|---|---|---|
| Brute force | tries many passwords against one account | a single targeted user |
| Dictionary | tries common passwords / wordlists (not all combinations) | a single user, efficiently |
| Credential stuffing | replays real username+password pairs from other breaches | many accounts, reused passwords |
| Password spraying | tries a few common passwords across many accounts | many users; evades per-account lockout |
email:password pairs leaked in past breaches and replay them against your login, betting that people reuse passwords. They're not guessing — they're using known-correct credentials from elsewhere, so a meaningful percentage just work. This is why "use a unique password per site" matters, why breach-password checks (below) help, and why MFA (Chapter 6) is the real backstop: a stuffed password alone shouldn't be enough.
Defence 1: Rate Limiting
The first line of defence is limiting how many login attempts a given source can make in a window. Track attempts and reject once a threshold is exceeded:
Rate limit on multiple keys — by IP, by account, and ideally combinations — because each alone has a gap: per-account limits don't stop spraying (one try per account across thousands), and per-IP limits don't stop a botnet (one try per IP across thousands of IPs). Pair coarse IP limits with per-account limits to cover both.
Defence 2: Account Lockout — Carefully
Locking an account after N failed attempts is classic, but it's a double-edged sword:
Defence 3: Don't Leak Which Part Was Wrong (User Enumeration)
A subtle but important defence: your login (and signup, and password-reset) responses must not reveal whether a username exists. Compare:
Distinct messages let an attacker enumerate valid accounts (which then makes credential stuffing and spraying far more efficient — they only target real accounts). Beyond the message text, watch for timing leaks: if a valid username runs the slow password hash but an invalid one returns instantly, the response-time difference reveals validity. Mitigate by running a dummy hash comparison even when the user doesn't exist, so both paths take similar time. The same generic-response discipline applies to signup ("if that email is new, we've sent a link") and password reset ("if that account exists, we've emailed instructions").
Defence 4: CAPTCHA & Step-Up Friction
Introduce friction that's cheap for humans but expensive for bots — after signs of abuse rather than on every login (to avoid harming UX). A CAPTCHA (or a modern proof-of-work / risk-based challenge) triggered after a few failures, or for traffic from suspicious IP ranges, sharply raises the cost of automated guessing. Risk-based authentication goes further: challenge harder when the login looks unusual (new device, new country, impossible travel) and stay frictionless when it looks normal.
Defence 5: Breached-Password & Strength Checks
Stop bad passwords at the source. Modern guidance (NIST) favours checking new passwords against breach corpora rather than imposing arbitrary composition rules:
- Block known-breached passwords — services like Have I Been Pwned's k-anonymity API let you reject passwords that appear in breach datasets without sending the password itself. This directly defangs credential stuffing.
- Encourage length over complexity — long passphrases beat short "P@ssw0rd!"-style strings; the old "must have an uppercase, number, and symbol" rules push users toward predictable patterns.
- Don't force frequent rotation — mandatory periodic changes lead to weaker, incremental passwords (
Spring2024!→Summer2024!); rotate only on suspected compromise.
Hands-On Exercises
Distinguish brute force, credential stuffing, and password spraying — how each guesses and why per-account lockout stops one but not the others. Then explain why rate limiting must key on both IP and account.
📄 View solutionShow how a login form leaks valid usernames via (a) different error messages and (b) response timing. Write the fixes for both (generic message + dummy hash), and explain why the same discipline must extend to signup and password-reset responses.
📄 View solutionExplain why naive "lock after 5 failures" account lockout can be turned into a denial-of-service and still fails to stop spraying. Design a better defence layering (backoff, temporary locks, IP+account rate limits, breach checks, step-up CAPTCHA, MFA) and justify the ordering.
📄 View solutionChapter 3 Quick Reference
- Brute force / dictionary — many guesses vs one account; credential stuffing — replays real breached pairs; spraying — few common passwords vs many accounts
- Credential stuffing is the dominant real-world ATO attack — known-correct credentials, exploiting password reuse
- Rate limit on multiple keys (IP and account) — per-account misses spraying, per-IP misses botnets
- Account lockout is double-edged — naive hard locks enable DoS against your own users & miss spraying; prefer backoff + temporary locks + step-up
- Prevent user enumeration — identical generic responses on login/signup/reset, and equalize timing (dummy hash when user absent)
- CAPTCHA / risk-based step-up after signs of abuse, not on every login
- Breach checks (HIBP k-anonymity) + length over complexity + no forced rotation (NIST guidance)
- MFA outranks all of these — it makes a guessed/stuffed password insufficient (Chapter 6)
- Next chapter: sessions & cookies — how the session works after login