Authentication Bugs

MFA Code Accepted After Use

A TOTP one-time code used to complete an MFA login can be submitted again in a second login attempt within the same 30-second validity window and still passes verification. The server validates the code against the current time window but does not record that this specific code has already been consumed, allowing an attacker who intercepted the code to replay it.

HighIntermediateSecurity testingManual testingAPI testing

// UNDERSTAND

// Symptoms

  • Submitting the MFA code 847392 once to log in succeeds, then immediately submitting the same code 847392 in a fresh login attempt within the same 30-second window also succeeds
  • The MFA step does not return an error such as 'This code has already been used'
  • The server-side used-code log or token table has no entry for the consumed code
  • An attacker who observes the 6-digit code during a legitimate login can immediately use it themselves in the same time window

// Root Cause

  • The server verifies the submitted TOTP code by checking it against the HMAC-based value for the current time window, but does not store consumed codes in a short-lived set (keyed on user_id + code + time_window). Any code valid for the current window passes on every submission within that window.
  • TOTP one-time codes are designed to be single-use within their validity window. The 'once only' guarantee requires server-side state to track which codes have already been accepted โ€” this state is absent.

// Where It Appears

  • Login flows with TOTP-based MFA (Google Authenticator, Authy, hardware tokens)
  • SMS OTP verification steps that do not mark codes as used after acceptance
  • Any multi-step authentication flow where a time-based code is validated without recording its use

// REPRODUCE & TEST

// How to Reproduce

  1. 01Log in as test@example.com with valid credentials to reach the MFA challenge step
  2. 02Open the authenticator app and note the current 6-digit code (e.g. 847392) and its remaining validity time โ€” proceed only if at least 10 seconds remain in the window
  3. 03Submit code 847392 to complete the MFA step; confirm the login succeeds
  4. 04Immediately log out
  5. 05Start a new login attempt as test@example.com and advance to the MFA challenge step
  6. 06Submit the same code 847392 again while still within the original 30-second window
  7. 07If the second login succeeds, the code was accepted a second time โ€” the replay bug is confirmed

// Test Data Needed

  • A test account at test@example.com with MFA enabled using a TOTP authenticator app
  • Access to the authenticator app to read the current code
  • Enough remaining time in the 30-second TOTP window to complete two login attempts โ€” perform the test at the start of a new window

// Manual Testing Ideas

  • Use the same 6-digit code in two consecutive login attempts within the same 30-second window and observe whether the second attempt succeeds
  • After a successful MFA login, immediately start a second login from a different browser or incognito window and try the same code
  • Test SMS OTP if the application offers it: receive the code, use it, then attempt to use it again before it expires
  • Check whether the application accepts TOTP codes from the previous time window (clock drift allowance) and whether those can also be replayed
  • Inspect server-side logs or the token/session table to confirm whether used codes are recorded

// API Testing Ideas

  • Authenticate as test@example.com with valid credentials to receive a session cookie or partial-auth token at the MFA step
  • Submit POST /api/auth/mfa with body { "code": "847392" } and the partial-auth token; assert the response is 200 with a full session token
  • Immediately submit POST /api/auth/mfa again with the same code 847392 and the same partial-auth token
  • Assert the second request returns 400 Bad Request or 401 Unauthorized with a message such as 'Code already used' โ€” if it returns 200, the replay bug is confirmed

// Automation Idea

Using a test account with a known TOTP seed, programmatically generate the current 6-digit code. Submit it to POST /api/auth/mfa and assert the first call returns 200. Immediately submit the same code again to POST /api/auth/mfa. Assert the second call returns 400 or 401. A 200 on the second call confirms the code is not invalidated after use.

// Expected Result

A TOTP code is accepted at most once per validity window. A second submission of the same code within the same window returns 400 or 401 with a message indicating the code has already been used.

// Actual Result (Example)

POST /api/auth/mfa with code 847392 returns 200 and a full session token. Immediately resubmitting code 847392 in a new login attempt (within the same 30-second window) also returns 200 and a new session token. The code is accepted twice.

// REPORT IT

Example Bug Report

Title
TOTP code 847392 accepted twice within the same 30-second window for test@example.com
Severity
High
Environment
Staging environment Test account: test@example.com with TOTP MFA enabled Authenticator app showing code 847392
Steps to Reproduce
  1. 01Log in as test@example.com with the correct password to reach the MFA step
  2. 02Note the current TOTP code from the authenticator app (847392) with at least 15 seconds remaining in the window
  3. 03Submit code 847392 at the MFA challenge; confirm the login succeeds
  4. 04Log out immediately
  5. 05Start a new login as test@example.com and advance to the MFA step (same 30-second window)
  6. 06Submit code 847392 again
Expected Result
The second submission is rejected with 'This code has already been used' or similar.
Actual Result
The second submission of code 847392 succeeds and a new authenticated session is created. The same TOTP code is accepted twice within the same 30-second validity window.
Impact
An attacker who observes a victim's TOTP code during a legitimate login โ€” via shoulder surfing, phishing, or network interception โ€” can immediately replay it to authenticate as that user before the 30-second window expires, bypassing MFA entirely.

// RELATED