WordPress / WordPress/two-factor

Email resend bypasses retry delay in 2FA challenge flow

Open
#847 1 comment 0 reactions 1 assignee View on GitHub

@dknauss is already working on this.

Since Mar 24, 2026.

Dominant language
PHP
Stars
825
Forks
187
Avg merge
2d 11h
Merged PRs (30d)
17

Description

Summary

The email provider handles resend in pre_process_authentication(), but Two_Factor_Core::process_provider() applies is_user_rate_limited() only afterward. As a result, resend requests are not subject to the same retry-delay gate as verification attempts.

Why this matters

For email-based 2FA, a user who has already hit the plugin's retry delay after invalid verification attempts can still trigger Resend Code. (The button is presented to them.) Each resend generates a fresh token via generate_and_email_token(), replacing the one that an attacker may have been trying to guess. This effectively resets the brute-force window while the rate limit is still active — the delay blocks verification attempts but not the token rotation that undermines it.

This is an inference from current source, not a confirmed exploit. It looks like an auth-flow / abuse-hardening gap rather than a full auth bypass.

It's also highly annoying if you get trapped by this! You're told you're blocked but prompted to send yourself an new code, which you can do — but the (valid) code won't get you logged in.

Verified code path

  1. Two_Factor_Core::process_provider() calls $provider->pre_process_authentication( $user ) before it checks is_user_rate_limited( $user ).
  2. Two_Factor_Email::pre_process_authentication() resends whenever two-factor-email-code-resend is present.
  3. The rate-limit check runs only later in process_provider(), so it does not gate the resend branch.

Current behavior from source

  • Resend is handled as provider pre-processing.
  • Rate limiting is applied only to the subsequent verification path.
  • I did not find a dedicated resend throttle in the email provider path.

Potential impact

  • Repeated resend requests can generate additional email traffic even when verification attempts are temporarily delayed.
  • Integrators embedding the provider UI inside another challenge flow may see resend behavior that does not match the plugin's normal retry messaging.

Suggested fix

Either:

  1. Move the rate-limit check ahead of provider pre-processing for resend-capable flows, or
  2. add a dedicated resend throttle in Two_Factor_Email::pre_process_authentication().

Related context

  • This seems adjacent to the broader throttling discussion in #477, but I could not find a report specifically about the email resend path surviving the existing retry-delay check.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.