TOTP doesn't fail. Your implementation does.
Why TOTP MFA gets bypassed: replay, wide skew windows, missing rate limits, seed theft, and origin-blind adversary-in-the-middle proxies.
TOTP is defined in RFC 6238. Six digits. Thirty-second time step. HMAC-SHA1 over a shared secret and a Unix-time counter, truncated to a decimal code. It is the most widely deployed second factor in enterprise authentication. It is also phishable, replayable, and - depending on how the validating server is written - brute-forceable. The protocol does not fail. The implementations around it do, and they do it the same way every time.
Start with what TOTP actually proves. The algorithm sits on top of HOTP, RFC 4226. Both endpoints hold the same symmetric seed. The client computes HMAC(seed, floor(unixtime / 30)), applies dynamic truncation, and reduces the result modulo 10^6. The server computes the same value and compares. A match proves one thing: the party submitting the code knows the current output of a secret both sides already share. It proves nothing about where the code was entered, which origin requested it, or whether the same code was already used thirty seconds ago by someone else. There is no channel binding. There is no origin binding. There is no proof of possession beyond knowledge of a six-digit number with a thirty-second shelf life. Every implementation flaw below is a consequence of building on that foundation and then cutting a corner.
The first flaw is replay. RFC 6238 section 5.2 states the validation server should reject a previously accepted OTP within its validity window. The word is a recommendation, not a mechanism, and a large share of implementations never track consumed codes. That maps to CWE-294, authentication bypass by capture-replay. A code captured at second 3 of a thirty-second window is valid until second 30. Any server that does not record the tuple of user, code, and time-step accepts that same code a second time. The entire adversary-in-the-middle class depends on this. Capture once, submit once, and the window does the rest.
The second flaw widens the first. Clock skew between client and server is real, so servers accept codes from adjacent time steps. RFC 6238 permits a small look-back and look-ahead. Some deployments set it to plus or minus one step. Others set it wider to cut support tickets. Every additional accepted step multiplies the number of codes valid at any instant. A plus-or-minus-three-step window means seven distinct six-digit codes authenticate right now. That is a direct downgrade to both replay tolerance and brute-force economics.
The third flaw is the absence of rate limiting. The keyspace is 10^6. That is small. It is only defensive because a given code lives for thirty seconds and because the server is supposed to lock the account after a handful of failures. Strip the lockout - CWE-307, improper restriction of excessive authentication attempts - and the arithmetic changes. With a wide acceptance window and no per-account throttle, the number of guesses a server will tolerate inside a single validity window stops being negligible. This is not a theoretical gap. Authentication endpoints that enforce lockout on password failure routinely forget to enforce it on the second-factor step, because the two checks live in different code paths and only one got the attention.
The truncation itself gives an attacker nothing. HOTP takes the low four bits of the final HMAC byte as an offset, reads four bytes from that position, masks the high bit, and reduces modulo 10^6. The operation is deterministic, with no secret-dependent branch and no timing side channel worth chasing. The weakness is never the math. It is the thirty-second constant and the uniform keyspace, which together define exactly how many guesses a window tolerates. A server that resets its failure counter at each new time step hands a fresh guessing budget to the same attacker every thirty seconds.
The fourth flaw is seed storage. The TOTP secret is symmetric and long-lived. It is provisioned once at enrollment and never rotated in most deployments. Servers store it to validate codes. When seeds are held in plaintext or under reversible encryption with a key sitting on the same host - CWE-312, cleartext storage of sensitive information - a database compromise is not a password-reset event. It is permanent. An attacker who exfiltrates the seed table generates valid codes offline, forever, for every enrolled user, with no interaction and no telemetry. There is no forward secrecy in TOTP. The seed is the factor.
The fifth flaw is enrollment. The secret is delivered as an otpauth:// URI, usually rendered as a QR code. That URI is the raw seed in Base32. It gets screenshotted, pasted into chat, emailed, and backed up. Authenticator apps that sync to cloud accounts replicate the seed to wherever that account lives. Every copy is the full factor. The provisioning step leaks the one value the entire scheme depends on staying secret, and it leaks it in a format designed to be easy to copy.
Now the flaw that drives real incidents. TOTP is phishable because it has no origin binding, and attackers industrialised that years ago. The technique is adversary-in-the-middle, MITRE T1557, paired with multi-factor authentication interception, T1111. The attacker stands up a reverse proxy between the victim and the real service. Tooling is mature and off-the-shelf - Evilginx, Modlishka, and the EvilProxy phishing-as-a-service platform. The victim lands on the proxy, sees the genuine login page relayed in real time, enters username, password, and the current TOTP code. The proxy forwards each credential to the legitimate service inside the thirty-second window. Sub-second relay. The service validates the code - it is a real code - and issues a session cookie. The proxy captures that cookie. That is steal web session cookie, T1539. The second factor was satisfied and then made irrelevant, because the thing of value was never the code. It was the authenticated session the code unlocked.
This is the mechanism behind the breaches practitioners already know. The EvilProxy platform lowered the skill floor for AitM to a subscription. The 0ktapus campaign - tracked against Scattered Spider - phished Okta-backed identities across more than a hundred organisations using exactly this pattern. In the 2022 wave that hit Twilio and was attempted against Cloudflare, the differentiator was the second factor itself. Cloudflare’s accounts were protected by origin-bound hardware security keys, so the relayed credentials were useless at a proxy origin that did not match. The accounts protected by codes were the ones that fell. Same phish. Different factor. Different outcome.
What defenders see is the hard part, because a phished-then-replayed code is indistinguishable from a legitimate one at the point of validation. The server receives a correct code inside the window and logs a successful MFA event. There is no failed-authentication signal, no malformed input, nothing that fires a rule on the auth event itself. The blind spot is structural. The detection has to move off the code and onto the session. The observable signals live in sign-in telemetry - authentication succeeding from a hosting-provider ASN rather than a residential or corporate range, because the proxy runs on a VPS; impossible-travel between the password entry and the session’s subsequent use; a user-agent or TLS fingerprint on the session that does not match the enrolled device; a session token replayed from a second IP minutes after issuance. Entra ID risk detections surface part of this as anomalous-token and unfamiliar-sign-in-properties signals. The correlation that matters is successful authentication immediately followed by the session being exercised from different infrastructure. Brute-force and replay attempts against the code are noisier in principle, but only if the validation path logs every submission - and many log only the pass or the final failure, which erases the signal that would have caught a guessing run.
The correlation that works is concrete. Join the successful authentication event to the session’s first resource access and alert when the two originate from different ASNs within a short interval. In Okta System Log terms, pair user.authentication.auth_via_mfa with the session’s subsequent activity and track the client IP and device fingerprint across them. In Entra ID, correlate a successful MFA sign-in with a riskDetection of type anomalousToken or a session exercised under a new user-agent string. None of these rules inspect the code. All of them watch the session the code released, which is the only place the attack leaves a mark.
The residual reality after every implementation flaw above is fixed: TOTP is still phishable. Enforce single-use code consumption, tighten the acceptance window to a single step, rate-limit and lock the second-factor path, encrypt seeds under a key the application host cannot read alone - do all of it, and a correctly implemented TOTP still has no channel binding and no origin binding. An adversary-in-the-middle proxy defeats a perfect TOTP deployment because the weakness is in what the protocol proves, not in how any one vendor coded it. The control that breaks the AitM chain is origin-bound, phishing-resistant authentication. WebAuthn and FIDO2 bind the assertion to the relying-party origin through the client data and the credential’s scoped key pair, so a proxy sitting on a lookalike origin receives a signature the real service rejects. That binding is the property TOTP never had. The Australian Signals Directorate’s Essential Eight now points MFA toward phishing-resistant methods for this reason. Where an active AitM campaign is suspected against production identities, the response is session revocation and escalation to the identity and incident-response teams, not a code rotation - rotating a seed does nothing to a stolen session that is already authenticated.
TOTP raised the cost of credential stuffing. It did not raise the cost of a proxy. Those are different attacks, and the second one is the one in the logs.
Keep Reading
session-hijackingVectra lifted bearer tokens off Teams disk
Why Microsoft Teams session tokens leak between workspace and consumer accounts, how bearer-token replay bypasses MFA, and what fires - and doesn't - in telemetry.
oauthOne bearer token, replayed from a residential proxy
How attackers abuse OAuth 2.0 at scale via consent phishing, device code flow, and service principal credentials - and why endpoint EDR sees none of it.
linux-kernelFrom $ to # in one unshare call
Technical breakdown of Linux kernel LPE CVEs - nf_tables UAF, Dirty Pipe, StackRot - exploit primitives, attack chains, and the telemetry gap defenders face.
Latest on the Wire
Full wire →- 1936 Electrical Control Room’s Hidden LegacyHacker News
- 40% of health facilities fail mock bird flu outbreak drillArs Technica
- AI Crushes Stratego, Solving Longtime Challenge on a BudgetHacker News
- AI Evangelism Sparks Industry DissatisfactionHacker News
New signal daily · RSS
Stay in the loop
New writing delivered when it's ready. No schedule, no spam.