From passwords to MFA
Passwords fail at scale: people reuse them, dumps fuel credential stuffing, and storage mistakes turn one breach into millions of cracked accounts. MFA helps but is routinely phished; only phishing-resistant factors like passkeys actually hold.
A breach hits a forum nobody on your team has ever heard of. Two weeks later your own login endpoint is taking 40,000 requests an hour, each one a real email-and-password pair, each one returning 200 OK for about 1 in 200 attempts. You weren’t breached. Your users were — somewhere else, years ago — and they reused the same password on you. The attacker didn’t crack anything. They replayed a list. This is credential stuffing, and it is the single most common way accounts get taken over today, precisely because it routes around every defense that assumes the attacker has to break your system.
By the end of this lesson you’ll know why passwords fail at scale, exactly how MFA gets bypassed in the field, and which factors are actually phishing-resistant.
Why passwords fail at scale
A password is a shared secret: the user knows it and your server knows it. That symmetry is the whole problem. The user reuses the secret across dozens of sites, so its safety is only as good as the worst site they ever typed it into — and you don’t control that site. Studies of large breach corpora put password reuse north of 50% of users; that single fact is why a dump from an unrelated service becomes an attack on yours.
Three failure modes compound:
- Reuse → credential stuffing. Attackers take billions of leaked
email:passwordpairs (the “Collection #1” dump alone held ~773M unique emails) and replay them against your login with rotating proxies. Success rates of 0.1–2% sound tiny until you multiply by millions of attempts: a 0.5% hit rate on 10M tries is 50,000 compromised accounts, and your logs just show a lot of valid logins. - Guessing → brute force and spraying. Against a single account, rate limits and lockouts help. Password spraying sidesteps them: try one common password (
Winter2024!) against ten thousand accounts, so no single account trips a lockout. Per-account counters never fire; you need per-source and per-credential-pattern detection. - Storage mistakes → offline cracking. If your password store leaks, the attacker’s clock starts. Plaintext or unsalted MD5/SHA-1 means the whole table is cracked in hours on a commodity GPU rig doing tens of billions of guesses per second. A correctly chosen slow, salted, memory-hard hash (Argon2id, scrypt, or bcrypt) is what buys you time — salt kills precomputed rainbow tables and forces per-account work, and the deliberate slowness caps the attacker’s guess rate.
The senior reflex: treat your password store as already leaked and design the hash so that even then, cracking is economically painful. And treat every successful login as possibly an attacker with a valid password — which is exactly the gap a second factor is meant to close.
How MFA helps — and how it’s bypassed
Multi-factor authentication requires evidence from more than one category: something you know (password), something you have (a phone, a security key), something you are (a fingerprint). The point isn’t that any one factor is strong; it’s that an attacker now needs to compromise two unrelated things at once. Against bulk credential stuffing this is devastating: the bot has the password but not the second factor, so the automated takeover dies. Microsoft and Google have both reported MFA blocking well over 99% of automated account-compromise attempts. That number is real — and it’s also where people stop thinking, which is the dangerous part.
MFA is not binary. The factors form a strength ladder, and a targeted attacker (as opposed to a bulk bot) defeats the weak rungs routinely:
- SMS one-time codes are phishable and interceptable. SIM-swap fraud — socially engineering a carrier to port the victim’s number to the attacker’s SIM — delivers the code straight to the attacker. SMS is better than nothing and far better than nothing against bots, but NIST has discouraged it as a primary factor for years.
- TOTP authenticator apps (the 6-digit code that rotates every 30 seconds, RFC 6238) remove the carrier from the loop — the secret lives on the device. But the code is still something the user can read and type, which means the user can be tricked into typing it into a fake site.
- Push notifications add a tap, but introduce MFA fatigue: spam the victim with prompts at 3am until they tap “approve” to make it stop. This is how Uber and others were breached. Number-matching (the prompt shows a number the user must enter) blunts it, but doesn’t eliminate the human in the loop.
The bypass that ties the weak rungs together is the real-time phishing proxy (Evilginx, Modlisha and friends). The victim lands on a pixel-perfect proxy of your login. They enter their password — the proxy relays it to the real site. The real site asks for the TOTP code — the proxy relays the prompt, the victim types the code, the proxy relays that too. The real site issues a session cookie, and the proxy steals it. The attacker now has an authenticated session and never needed the password or the code again. Every factor the user types is replayable through this man-in-the-middle, which is the whole reason “phishing-resistant” is a distinct, harder property.
▸Why this works
Why doesn’t a one-time code stop a phishing proxy if it’s only valid for 30 seconds? Because the proxy is live. It isn’t saving the code for later — it’s relaying it the instant the victim types it, inside the same 30-second window, to the genuine site. The OTP does exactly its job (proves a fresh code) and still loses, because nothing in the protocol binds the code to which site the user is actually talking to. That missing bind — between the credential and the origin — is the precise gap phishing-resistant factors close.
Phishing-resistant factors
A factor is phishing-resistant when stealing what the user typed gives the attacker nothing — because there’s nothing typeable to steal. The mechanism is origin-bound public-key cryptography, standardized as WebAuthn / FIDO2 and shipped to users as passkeys and hardware security keys.
Here’s the mechanism that matters. At registration, the authenticator (a YubiKey, or the secure element in a phone) generates a new key pair scoped to your exact origin, https://yourbank.com. The private key never leaves the device; the server stores only the public key. At login, the server sends a random challenge; the authenticator signs it with the private key and the browser includes the real origin in what gets signed. Two consequences fall out of that single design choice:
- There is no shared secret to phish. The user never types anything an attacker could replay. A signature for one challenge is useless for the next.
- The origin binding kills the proxy. If the victim is on
yourbank.evil.com, the browser signs that origin — and your server, expectingyourbank.com, rejects the signature. The Evilginx relay collapses, because the credential refuses to work anywhere but the genuine site. This is the property SMS, TOTP, and push all lack.
| Factor | Category | Stops bulk stuffing? | Stops real-time phishing? | Defeated by |
|---|---|---|---|---|
| Password only | Know | No | No | Reuse, dumps, cracking |
| SMS OTP | Have (weak) | Yes | No | SIM swap, proxy relay |
| TOTP app | Have | Yes | No | Real-time proxy (typed code) |
| Push + number-match | Have | Yes | Partly | MFA fatigue, relay |
| Passkey / security key | Have + are | Yes | Yes | Device theft + local unlock |
The senior takeaway is a threat-tiered one. Any MFA crushes the bulk-bot problem, so the cheapest big win is enabling some second factor for everyone. But for accounts that matter — admins, finance, anyone with production access — typed factors are not enough, because those are exactly the accounts a human attacker will phish in real time. There, phishing-resistant WebAuthn is the bar. And don’t let MFA become a single point of failure: weak account-recovery (“lost my phone, text my old number”) is the back door that quietly downgrades your strongest factor to your weakest one.
Your admins keep getting phished: attackers stand up a fake login proxy, relay the password and the TOTP code in real time, and steal the session cookie. TOTP MFA is already enabled. Pick the fix that actually closes this.
Your login is getting millions of attempts using real email/password pairs, with a ~0.5% success rate and no single account locking out. What is this, and what's the root enabler?
Why does a passkey (WebAuthn) defeat a real-time phishing proxy when a TOTP code does not?
Order these auth factors from weakest to strongest against a targeted, human-driven attacker:
- 1 Password only (reused, phishable, crackable)
- 2 SMS OTP (phishable + SIM-swappable)
- 3 TOTP app (phishable: code is typed)
- 4 Push + number-matching (resists fatigue, still relayable)
- 5 Passkey / security key (origin-bound, phishing-resistant)
- 01Explain why passwords fail at scale and name the three compounding failure modes.
- 02What makes a factor phishing-resistant, and why do SMS, TOTP, and push all fail that bar while passkeys pass it?
Passwords fail at scale because they’re shared secrets users reuse: a breach at any site becomes credential stuffing against yours, where billions of leaked pairs are replayed and even a fraction-of-a-percent hit rate produces mass account takeover. Spraying dodges per-account lockouts, and a leaked store gets cracked fast unless it’s hashed with a slow, salted, memory-hard function (Argon2id, scrypt, bcrypt). MFA is the answer to stuffing — it blocks over 99% of automated takeovers — but it isn’t binary. SMS is phishable and SIM-swappable, TOTP and push are relayed by real-time phishing proxies that forward the typed code and steal the session cookie, and push adds MFA-fatigue spam. The factor that actually holds against a targeted human is phishing-resistant WebAuthn: a passkey or security key signs an origin-bound challenge with a private key that never leaves the device, so a signature made on a fake domain is rejected and there’s no secret to relay. So the senior posture is tiered — enable any MFA for everyone to kill the bots, require passkeys for the accounts that matter, hash as if the store is already leaked, and watch that account recovery doesn’t become the back door around all of it.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.