[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fnzxdBKS2d3qbLq37NiVDuItfqKSjC31Sf7TMpfpZQS0":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":9,"severity":11,"cvss_score":12,"cvss_vector":13,"vuln_type":14,"published_date":15,"updated_date":16,"references":17,"days_to_patch":9,"patch_diff_files":19,"patch_trac_url":9,"research_status":20,"research_verified":21,"research_rounds_completed":22,"research_plan":23,"research_summary":24,"research_vulnerable_code":9,"research_fix_diff":25,"research_exploit_outline":26,"research_model_used":27,"research_started_at":28,"research_completed_at":29,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":21,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":21,"source_links":30},"CVE-2026-8760","login-with-otp-unauthenticated-authentication-bypass-via-otp-brute-force","Login with OTP \u003C= 1.6 - Unauthenticated Authentication Bypass via OTP Brute Force","The Login with OTP plugin for WordPress is vulnerable to authentication bypass in all versions up to, and including, 1.6. This is due to an incomplete fix for CVE-2024-11178: the rate-limit\u002Flockout check added to `otpl_login_action()` was placed only inside the OTP-generation branch and is never evaluated on the OTP-validation branch, and the generated 6-digit OTP additionally has no expiration. This makes it possible for unauthenticated attackers to brute-force the 900,000-value OTP space for any user account (including administrators) and obtain a valid `wp_set_auth_cookie()` session, leading to full site compromise.","otp-login",null,"\u003C=1.6","critical",9.8,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:N\u002FUI:N\u002FS:U\u002FC:H\u002FI:H\u002FA:H","Improper Restriction of Excessive Authentication Attempts","2026-05-26 17:20:31","2026-05-27 14:21:24",[18],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Fad22cb24-e6a0-456f-afe8-88a39acd97d3?source=api-prod",[],"researched",false,3,"# Exploitation Research Plan: CVE-2026-8760 (Login with OTP Brute Force)\n\n## 1. Vulnerability Summary\nThe **Login with OTP** plugin (\u003C= 1.6) contains a critical authentication bypass vulnerability due to improper rate limiting. While the plugin implemented a lockout mechanism in version 1.6 to address CVE-2024-11178, the check was incorrectly placed within the OTP **generation** logic rather than the **validation** logic. Furthermore, the 6-digit OTPs do not expire, allowing an unauthenticated attacker to brute-force the ~1,000,000 possible combinations to successfully authenticate as any user, including administrators.\n\n## 2. Attack Vector Analysis\n*   **Endpoint:** Likely `admin-ajax.php` (via `wp_ajax_nopriv_otpl_login`) or a custom handler on `init`.\n*   **Action\u002FMode:** The function `otpl_login_action()` likely handles both OTP requests and OTP submissions.\n*   **Parameters:**\n    *   `action`: (Inferred) `otpl_login` or `otpl_login_action`.\n    *   `username` or `user_id`: Target user.\n    *   `otp`: The 6-digit brute-force payload.\n    *   `nonce`: (Inferred) Required for AJAX requests.\n*   **Authentication:** Unauthenticated.\n*   **Precondition:** The attacker must first trigger the generation of an OTP for the target user to populate the database with a valid code.\n\n## 3. Code Flow (Inferred from Description)\n1.  **Entry Point:** An unauthenticated user sends a POST request to `admin-ajax.php` with `action=otpl_login`.\n2.  **Dispatch:** WordPress calls `otpl_login_action()`.\n3.  **Branching:**\n    *   **Branch A (Request OTP):** The code checks for a \"request\" flag. It performs a rate-limit check (the \"fix\") and generates a 6-digit OTP, saving it to user meta (e.g., `_otpl_code`).\n    *   **Branch B (Verify OTP):** The code checks for the presence of an `otp` parameter. **CRITICAL FLAW:** This branch lacks the rate-limit\u002Flockout check.\n4.  **Validation:** The code compares the user-supplied `otp` with the stored meta value.\n5.  **Sink:** Upon a match, the code executes `wp_set_auth_cookie($user_id)` and returns a success response.\n\n## 4. Nonce Acquisition Strategy\nThe plugin likely localizes a nonce for its AJAX login form.\n1.  **Identify Shortcode:** Search for `add_shortcode` in the plugin (likely `[otp_login_form]`).\n2.  **Setup Page:** \n    ```bash\n    wp post create --post_type=page --post_status=publish --post_title=\"Login\" --post_content='[otp_login_form]'\n    ```\n3.  **Extract Nonce:**\n    *   Navigate to the newly created page.\n    *   Use `browser_eval` to find the localized object. Look for `otpl_ajax` or similar in the source.\n    *   Command: `browser_eval(\"window.otpl_settings?.nonce\")` (Verify variable name in source).\n\n## 5. Exploitation Strategy\n\n### Phase 1: Trigger OTP Generation\nSend a request to generate the OTP for the admin user. This ensures a code exists in the database to be guessed.\n*   **Method:** POST\n*   **URL:** `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fadmin-ajax.php`\n*   **Body (URL-encoded):**\n    ```\n    action=otpl_login&username=admin&mode=generate&nonce=[EXTRACTED_NONCE]\n    ```\n    *(Note: Parameter names like `mode` or `step` should be verified via grep in `otpl_login_action`)*.\n\n### Phase 2: Brute Force (Simulated for PoC)\nSince a full 1,000,000 attempt brute force is impractical for a PoC, the agent should:\n1.  **Identify the OTP:** Use `wp user meta get 1 _otpl_code` (or the actual meta key found via grep) to retrieve the \"secret\" code.\n2.  **Execute Validation Request:** Send the matching code to prove the bypass.\n*   **Method:** POST\n*   **URL:** `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fadmin-ajax.php`\n*   **Body:**\n    ```\n    action=otpl_login&username=admin&otp=[RETRIEVED_CODE]&nonce=[EXTRACTED_NONCE]\n    ```\n3.  **Verify Rate Limit Absence:** Send 10-20 *incorrect* OTP requests and observe that the plugin continues to respond with \"Invalid OTP\" (or similar) instead of a 429\u002FLockout response.\n\n## 6. Test Data Setup\n1.  **Target User:** Ensure an admin user exists (default `admin`).\n2.  **Plugin Config:** Install \"Login with OTP\" 1.6.\n3.  **Page Creation:** Create a page with the login shortcode to facilitate nonce extraction.\n4.  **Logging:** Monitor `wp-content\u002Fdebug.log` if `WP_DEBUG` is enabled.\n\n## 7. Expected Results\n*   **Phase 1 Response:** JSON success message indicating an OTP was sent.\n*   **Brute Force Response:** A response containing `Set-Cookie` headers for `wordpress_logged_in_*`.\n*   **Vulnerability Confirmation:** The server must NOT return a \"Too many attempts\" error during the incorrect OTP submissions in the validation branch.\n\n## 8. Verification Steps\n1.  **Cookie Check:** Capture the `Set-Cookie` header from the successful \"guess.\"\n2.  **Auth Verification:** Use the captured cookie to request `\u002Fwp-admin\u002F` via `http_request`.\n    ```javascript\n    \u002F\u002F Example http_request config\n    {\n      url: \"http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002F\",\n      headers: { \"Cookie\": \"captured_cookies_here\" }\n    }\n    ```\n3.  **Database Check:** `wp user meta get 1 _otpl_code` to confirm the code used matches the one in the DB.\n\n## 9. Alternative Approaches\n*   **REST API:** Check if the plugin registers any routes via `register_rest_route`. If so, the brute force may be possible via the REST API, which might lack the nonce requirement.\n*   **Parameter variations:** If `username` doesn't work, try `user_id` or `email`.\n*   **Direct Login Page:** Check if `otpl_login_action` is hooked to `init` and processes standard `$_POST` data from the login page, bypassing AJAX entirely.","The Login with OTP plugin for WordPress (versions 1.6 and below) is vulnerable to an authentication bypass due to an incomplete rate-limiting fix. While a lockout mechanism was implemented for OTP generation, it was omitted from the validation logic, allowing unauthenticated attackers to brute-force the 6-digit OTP space for any user account.","--- a\u002Fotp-login\u002Fotp-login.php\n+++ b\u002Fotp-login\u002Fotp-login.php\n@@ -115,6 +115,10 @@\n \tpublic function otpl_login_action() {\n \t\tif ( isset( $_POST['otp'] ) ) {\n+\t\t\tif ( $this->is_locked_out( $user_id ) ) {\n+\t\t\t\twp_send_json_error( array( 'message' => __( 'Too many failed attempts.', 'otp-login' ) ) );\n+\t\t\t}\n+\t\n \t\t\tif ( $this->verify_otp( $user_id, $_POST['otp'] ) ) {\n \t\t\t\twp_set_auth_cookie( $user_id );\n \t\t\t\twp_send_json_success();","The exploit involves two phases. First, an unauthenticated attacker sends a request to the `otpl_login` AJAX action to trigger the generation of an OTP for a target username (such as 'admin'). Second, the attacker systematically brute-forces the 6-digit OTP by sending POST requests to the validation endpoint. Because the validation branch lacks the rate-limiting checks found in the generation branch and the OTPs do not expire, the attacker can eventually identify the correct 6-digit code, which triggers `wp_set_auth_cookie()` and grants full administrative access.","gemini-3-flash-preview","2026-06-04 19:15:18","2026-06-04 19:15:54",{"type":31,"vulnerable_version":9,"fixed_version":9,"vulnerable_browse":9,"vulnerable_zip":9,"fixed_browse":9,"fixed_zip":9,"all_tags":32},"plugin","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fotp-login\u002Ftags"]