Weak OTP and MFA throttling

Application-level weaknesses in second-factor authentication that let an attacker who has (or fakes) the first factor reach a fully authenticated session. The flaws fall into three families: brute-forcing weak one-time codes, bypassing the rate limits meant to stop that, and logic flaws where the MFA step can be skipped or the code is not bound to the right user.

Overview#

The canonical flow is two-stage: stage one validates username:password; stage two validates a second factor (a TOTP from an authenticator app, an SMS/email OTP, a push approval, or a backup code). The security-critical invariant is that stage two must be statefully bound to the exact user who passed stage one, and the post-MFA session must be unusable until stage two succeeds.

PortSwigger frames the core design flaw: when the user enters a password and is then prompted for a code on a separate page, they are effectively "logged in" before the code is checked. A server that issues a session identifying the pending user before verifying the second factor is the root cause of most breaks below. The keyspace compounds it: a 4-digit code has 10,000 values, a 6-digit code about 1,000,000, so without server-side throttling the second factor degrades to a brute-force-solvable challenge.

Exploitation#

Brute-forcing the OTP#

With a short numeric code and no enforced attempt cap, enumerate the keyspace against the verify endpoint. OWASP warns that apps often accept codes from a window on either side of the current TOTP (previous/current/next), multiplying your odds; if several codes are simultaneously valid, sustained guessing reaches high success probability within hours.

A representative verify request to drive with Burp Intruder:

code
POST /login2 HTTP/1.1
Cookie: session=<post-password session>
Content-Type: application/x-www-form-urlencoded

mfa-code=0000

Set the payload position on mfa-code, generate 0000-9999, and use a session-handling macro to replay the preceding login requests so each guess carries a fresh valid session (apps that log you out after two wrong codes are defeated this way, since the protection is cosmetic). Success is typically an HTTP 302 to the account page.

Bypassing the rate limit#

When a limiter exists, attack its key rather than the code:

  • Rotate the client identity if the limiter is IP-keyed: spoof X-Forwarded-For (also X-Real-IP, X-Client-IP, Forwarded) per request; test non-standard headers (X-Debug) that may disable MFA entirely.
  • Reset the counter: re-request a fresh code, or change the username/account parameter so the counter keys on a different identity.
  • Tamper the throttle parameter: where a request field carries the limit state, send it empty or null.
  • Concurrency / race: fire many verify requests in parallel (Turbo Intruder single-packet attack) so they are checked before the counter increments.

Skipping the MFA step (logic flaws)#

Because the session may already be "logged in," force-browse straight to a post-MFA endpoint (GET /my-account) and see whether it loads without the second step. Tamper response-driven client logic ("verified":false to true), watch for status-code tells (302 vs 200), or route into an MFA-less flow (OWASP cites changing an Azure AD B2C policy B2C_1_SignInWithMFA to B2C_1_SignIn).

Code not bound to the user#

PortSwigger's flagship example: stage one sets Set-Cookie: account=carlos, and the verify request trusts that cookie (or a verify body parameter) to decide whose code is checked. Change it to a victim and you generate and validate a code against an account you never authenticated as:

code
POST /login-steps/second HTTP/1.1
Cookie: account=victim-user

verify=victim-user&mfa-code=1234

Related logic flaws: code reuse (no single-use), no expiry (stale codes accepted), and codes valid across users or sessions.

Tokens around MFA#

  • Leaked OTP: inspect the issuance response body/headers; codes are sometimes returned for debugging.
  • Backup / recovery codes: often longer-lived and weakly throttled; brute-force or replay them and check single-use.
  • "Remember device" / stay-logged-in cookies: if forgeable, they bypass MFA. PortSwigger's lab cookie is base64(username + ':' + md5(password)), an unsalted MD5 of the password that can be brute-forced offline.
  • SMS / SIM-swap: SMS OTP is vulnerable to SS7 interception and SIM-swap; also test changing a phone-number parameter so the code is delivered to an attacker-controlled number.

Tools#

  • Burp Suite: Intruder (code brute force), Turbo Intruder (race/concurrency), session-handling macros, and the Param Miner extension for header discovery.

References#

Cookie Consent

We use cookies to enhance your experience. Learn more