Introduction
An incomplete security fix in the WordPress "Login with OTP" plugin has left the same brute force attack surface wide open that it was supposed to close, allowing any unauthenticated attacker to take over administrator accounts by guessing a 6 digit OTP with no rate limiting on the validation endpoint. The vulnerability, tracked as CVE-2026-8760 with a CVSS score of 9.8, is a direct descendant of CVE-2024-11178 and demonstrates how a misplaced security control can create a false sense of protection while preserving the original attack vector.
The "Login with OTP" plugin (slug: otp-login) provides OTP based authentication for WordPress login forms, supporting both admin and frontend login flows with customization options including popup login and redirect URLs. It is developed by an individual contributor on WordPress.org under the handle "india-web-developer" and is listed among the OTP category plugins in the WordPress plugin directory.
Technical Information
Root Cause: Misplaced Rate Limiting
The vulnerability resides in the otpl_login_action() function within otpl-class.php. This single function handles both OTP generation and OTP validation through one AJAX endpoint. The function branches at line 345 based on whether the email_otp POST parameter is empty:
- If
email_otpis empty, execution enters the OTP generation branch (lines 345 through 418). - If
email_otpis non-empty, execution enters the OTP validation branch (lines 419 through 453).
The developer's attempted fix for CVE-2024-11178 added a lockout mechanism at lines 361 through 395 that retrieves otpl_login_attempts and otpl_last_failed_time from user meta, compares the failed attempt count against $max_attempts, and blocks further requests during the lockout window. The critical problem: this entire lockout check exists exclusively within the OTP generation branch. The OTP validation branch at lines 419 through 453 contains no corresponding rate limit check whatsoever.
This means an attacker can submit unlimited OTP guesses through the validation endpoint without ever triggering the lockout mechanism.
OTP Generation Weaknesses
At line 400, the OTP is generated and stored as follows:
$newotp = wp_rand(100000, 999999); update_user_meta($user_id, "emilotp", $newotp);
This produces a 6 digit numeric code with a search space of exactly 900,000 values. No timestamp is stored alongside the OTP, meaning it remains valid indefinitely. The validation code at lines 419 through 453 never checks how long ago the OTP was generated, so an OTP created hours or even days earlier remains exploitable.
Unprotected Validation Path
The validation logic performs a direct comparison at line 424:
if ( $db_otp == $email_otp && $validateotp != 0 ) {
No throttling or rate limiting precedes this check. While lines 442 through 446 do increment otpl_login_attempts after a failed match, this counter is never consulted before allowing the next validation attempt. The lockout logic that would enforce a block based on this counter resides only in the generation branch and is therefore never evaluated during validation. The failed attempt counter is, in effect, decorative.
Upon a successful OTP match, lines 426 through 427 immediately establish an authenticated session:
wp_set_current_user($user_id, $user->user_login); wp_set_auth_cookie($user_id);
No additional verification factors are required. No OTP age check is performed. The attacker receives a fully authenticated WordPress session the moment a valid OTP is discovered.
Additional Weaknesses in the Code
Beyond the primary rate limiting misplacement, the source code reveals several compounding weaknesses:
| Weakness | Location | Impact |
|---|---|---|
| Rate limit only in generation branch | Lines 345 vs 361 through 395 | Unlimited OTP validation attempts |
| No OTP expiration | Lines 400 through 402 | OTP remains valid indefinitely |
| No throttle on validation attempts | Lines 419 through 453 | Brute force feasible within minutes |
| Nonce not rotated per attempt | Lines 189, 330 | Same nonce reusable for unlimited requests |
otplzplussecurity check always bypassed | Lines 190, 339 through 342 | Dummy security check with no effect |
| No IP based blocking | Entire function | Cross account brute force possible |
| Failed attempt counter increments but never blocks | Lines 442 through 446 | Counter is decorative, not enforced |
Attack Flow
A practical exploitation sequence works as follows:
-
Trigger OTP generation: The attacker sends a POST request to the AJAX endpoint with an empty
email_otpparameter and the target user's email address. This causes the plugin to generate a 6 digit OTP and store it inwp_usermetaunder the key"emilotp". -
Brute force the validation endpoint: The attacker sends rapid POST requests to the same AJAX endpoint, each containing a different 6 digit guess in the
email_otpparameter. Because no rate limiting applies to the validation path, the attacker can try thousands of OTPs per minute. -
Statistical expectation: With 900,000 possible values, an average of 450,000 attempts are needed to find the correct OTP. At 1,000 requests per second, this completes in approximately 10 to 15 minutes.
-
Session establishment: Upon matching the OTP,
wp_set_auth_cookie()issues a valid authenticated session. If the targeted account is an administrator, the attacker gains complete control over the WordPress installation.
The attack requires no authentication (PR:N), no user interaction (UI:N), and operates entirely over the network (AV:N), consistent with the CVSS vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H.
Relationship to CVE-2024-11178
CVE-2024-11178, published December 6, 2024, was the original vulnerability in this plugin affecting versions up to 1.4.2. It was classified under CWE-288 (Authentication Bypass Using an Alternate Path or Channel) and had the same fundamental root cause: weak 6 digit OTPs with no attempt or time limits on verification. The developer's attempted fix in version 1.5/1.6 added the rate limit/lockout mechanism but placed it only on the OTP generation branch. This incomplete remediation transformed the vulnerability classification from CWE-288 to CWE-307 (Improper Restriction of Excessive Authentication Attempts) while preserving the same practical attack surface.
From the attacker's perspective, the exploitation method is essentially unchanged across both CVEs. The same brute force of 900,000 OTP values works against both the original and the "fixed" version.
Affected Systems and Versions
All versions of the "Login with OTP" WordPress plugin (slug: otp-login) up to and including version 1.6 are affected. Version 1.6 is the latest available version as of May 27, 2026.
No patched version exists. The Wordfence advisory recommends uninstalling the plugin entirely.
Vendor Security History
The Login with OTP plugin has accumulated two critical authentication bypass vulnerabilities in succession:
| CVE | Affected Versions | CWE | Root Cause | Published Date |
|---|---|---|---|---|
| CVE-2024-11178 | Versions up to 1.4.2 | CWE-288 | Weak 6 digit OTP with no attempt or time limits | December 6, 2024 |
| CVE-2026-8760 | Versions up to 1.6 | CWE-307 | Incomplete fix: rate limit only on generation branch, no OTP expiration | May 27, 2026 |
The progression from CVE-2024-11178 to CVE-2026-8760 reveals a pattern where the developer treated OTP generation as the sensitive operation requiring protection, when in fact OTP validation is the endpoint an attacker targets during brute force. This mismatch between security control placement and actual attack vector is the defining characteristic of the current vulnerability.
The plugin has not been updated in approximately 11 months, suggesting limited ongoing maintenance. For a security sensitive authentication plugin, this level of inactivity is a significant risk factor.
This pattern is not unique to this plugin. Three separate WordPress OTP plugins (Login with OTP, DIGITS, and User Verification by PickPlugins) have exhibited nearly identical vulnerabilities in the 2024 through 2026 timeframe, suggesting that OTP based authentication in WordPress plugins is a systemic weakness category. CVE-2025-4094 in the DIGITS plugin had a public exploit published on Exploit-DB (entry 52307), and CVE-2026-7458 in User Verification by PickPlugins involved an almost identical unauthenticated authentication bypass via brute force of OTPs on a validation endpoint lacking rate limiting.
References
- NVD: CVE-2026-8760
- Wordfence Advisory: Login with OTP <= 1.6 Authentication Bypass
- NVD: CVE-2024-11178 (Original Vulnerability)
- Source Code: otpl-class.php Line 361 (Rate Limit in Generation Branch)
- Source Code: otpl-class.php Line 419 (Validation Branch Entry)
- Source Code: otpl-class.php Line 424 (OTP Comparison)
- Source Code: otpl-class.php Line 427 (Session Cookie)
- Source Code: trunk otpl-class.php Line 361
- Source Code: trunk otpl-class.php Line 419
- Source Code: trunk otpl-class.php Line 424
- Source Code: trunk otpl-class.php Line 427
- CWE-307: Improper Restriction of Excessive Authentication Attempts
- WordPress Plugin Page: Login with OTP
- WPScan: Login With OTP < 1.5 Authentication Bypass
- Wordfence Brute Force Protection Documentation
- Exploit-DB 52307: WordPress Digits Plugin Authentication Bypass
- Wordfence Blog: 200,000 WordPress Sites at Risk from Burst Statistics Plugin



