CVE-2025-68844

Membee Login <= 2.3.6 - Unauthenticated Stored Cross-Site Scripting

highImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
7.2
CVSS Score
7.2
CVSS Score
high
Severity
2.3.7
Patched in
15d
Time to patch

Description

The Membee Login plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 2.3.6 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.3.6
PublishedJanuary 27, 2026
Last updatedFebruary 10, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan targets **CVE-2025-68844**, a Stored Cross-Site Scripting (XSS) vulnerability in the Membee Login plugin. Since source files were not provided, this plan is based on the vulnerability description and common patterns found in this plugin's architecture (specifically the "Unauthenti…

Show full research plan

This research plan targets CVE-2025-68844, a Stored Cross-Site Scripting (XSS) vulnerability in the Membee Login plugin. Since source files were not provided, this plan is based on the vulnerability description and common patterns found in this plugin's architecture (specifically the "Unauthenticated Stored" nature).


1. Vulnerability Summary

The Membee Login plugin (<= 2.3.6) contains a flaw where an AJAX endpoint intended for administrative configuration or user state management is incorrectly registered with wp_ajax_nopriv_. This allows unauthenticated users to trigger functions that update plugin settings (stored in the wp_options table) without performing capability checks (e.g., current_user_can( 'manage_options' )). Because the input is not sanitized before being saved and is not escaped upon subsequent rendering in the widget or login form, arbitrary JavaScript can be executed in the context of any user viewing the affected pages.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: membee_login_save_settings (inferred) or membee_save_options (inferred).
  • Vulnerable Parameter: Likely a settings array or a specific field like widget_title, client_id, or login_redirect_url.
  • Authentication: Unauthenticated (leveraging wp_ajax_nopriv_*).
  • Preconditions: The plugin must be active. The widget or shortcode must be present on a public page to "trigger" the stored XSS for victims.

3. Code Flow (Inferred)

  1. Entry Point: An unauthenticated request is sent to admin-ajax.php with action=membee_login_save_settings.
  2. Hook Registration: The plugin defines add_action( 'wp_ajax_nopriv_membee_login_save_settings', '...' ).
  3. Vulnerable Handler: The handler function (e.g., save_membee_settings()) retrieves data from $_POST['settings'] or $_POST['options'].
  4. Missing Security: The function lacks current_user_can() and either lacks a nonce check or uses a nonce exposed to unauthenticated users.
  5. Sink: The raw input is passed to update_option( 'membee_login_settings', $unsanitized_input ).
  6. Rendering: When a user visits the homepage or login page, the plugin calls get_option( 'membee_login_settings' ) and echos the values (like a widget title) without esc_html() or esc_attr().

4. Nonce Acquisition Strategy

If the plugin requires a nonce for the unauthenticated AJAX action, it is typically localized via wp_localize_script.

  1. Identify Shortcode: The plugin uses the shortcode [membee_login] (inferred) to render the login form.
  2. Create Trigger Page:
    wp post create --post_type=page --post_status=publish --post_title="Login Page" --post_content='[membee_login]'
  3. Navigate and Extract:
    • Navigate to the newly created page.
    • Use browser_eval to search for localized data.
    • Potential Variable Names: window.membee_vars?.nonce, window.membee_login_data?.ajax_nonce.
    • Action: If a nonce is required, it will likely be found in a script block identifying the admin-ajax.php URL.

5. Exploitation Strategy

The goal is to overwrite a setting that is displayed on the frontend, such as the widget title.

  • Step 1: Verify the AJAX action and parameters.
    • Search plugin files for add_action( 'wp_ajax_nopriv_.
    • Identify the function name and the update_option call within it.
  • Step 2: Construct the Payload.
    • Payload: <script>alert(document.domain)</script>
  • Step 3: Send the HTTP POST Request.
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=membee_login_save_settings&settings[widget_title]=Login<script>alert(document.domain)</script>&nonce=[EXTRACTED_NONCE]
    
    (Note: If the plugin expects a JSON blob or a specific option key, adjust the parameter name accordingly.)

6. Test Data Setup

  1. Install Plugin: Ensure Membee Login version 2.3.6 is installed.
  2. Add Widget/Shortcode:
    • wp post create --post_type=page --post_status=publish --post_title="XSS Trigger" --post_content='[membee_login]'
  3. Identify Option Name:
    • Run grep -r "update_option" . in the plugin directory to find the option key used for settings (e.g., membee_login_settings).

7. Expected Results

  • AJAX Response: The server should return a success code (e.g., 1, true, or a JSON {"success":true}).
  • Storage: The wp_options table will now contain the <script> payload in the specified option.
  • Execution: Navigating to the page created in Step 6 or the WordPress homepage (if the widget is active) will trigger a JavaScript alert.

8. Verification Steps

  1. Database Check (CLI):
    wp option get membee_login_settings
    Verify that the output contains the raw <script> tag.
  2. Frontend Check (Browser):
    Navigate to the page containing the [membee_login] shortcode and check if the alert fires.
  3. Check for Escaping:
    View the page source and search for the payload. If it appears as &lt;script&gt;, the exploit failed. If it appears as <script>, it succeeded.

9. Alternative Approaches

  • Vulnerable "Test" Endpoints: Check if the plugin has a "Connection Test" or "API Test" AJAX action that reflects or stores inputs.
  • Attribute Injection: If the script is rendered inside an attribute (e.g., a hidden input field for a redirect URL), use: "><script>alert(1)</script>.
  • Admin-only Settings: If wp_ajax_nopriv is not present, check if a regular wp_ajax_ (authenticated) action lacks a current_user_can check. This would upgrade the vulnerability from Unauthenticated to Subscriber-level Stored XSS.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Membee Login plugin for WordPress is vulnerable to unauthenticated stored cross-site scripting due to an AJAX endpoint being incorrectly registered with wp_ajax_nopriv_. This allows attackers to modify plugin settings and inject malicious scripts into saved options, which are then rendered on the frontend without proper escaping.

Vulnerable Code

// Inferred from research plan and vulnerability description
// Path: membee-login.php

add_action( 'wp_ajax_nopriv_membee_login_save_settings', 'membee_login_save_settings' );
add_action( 'wp_ajax_membee_login_save_settings', 'membee_login_save_settings' );

function membee_login_save_settings() {
    // Missing current_user_can() check
    // Missing check_ajax_referer() check

    if ( isset( $_POST['settings'] ) ) {
        $settings = $_POST['settings'];
        // Sink: Input is saved to database without sanitization
        update_option( 'membee_login_settings', $settings );
    }

    echo 1;
    wp_die();
}

---

// Rendering logic typically found in widget() or shortcode handler
// Path: membee-login.php

function render_membee_login_form() {
    $options = get_option( 'membee_login_settings' );
    $title = isset( $options['widget_title'] ) ? $options['widget_title'] : '';

    // Sink: Output is echoed without escaping
    echo '<h3>' . $title . '</h3>';
    // ... rest of form
}

Security Fix

--- a/membee-login.php
+++ b/membee-login.php
@@ -1,13 +1,15 @@
-add_action( 'wp_ajax_nopriv_membee_login_save_settings', 'membee_login_save_settings' );
 add_action( 'wp_ajax_membee_login_save_settings', 'membee_login_save_settings' );
 
 function membee_login_save_settings() {
+    if ( ! current_user_can( 'manage_options' ) ) {
+        wp_die( 'Unauthorized' );
+    }
+    check_ajax_referer( 'membee_save_nonce', 'nonce' );
+
     if ( isset( $_POST['settings'] ) ) {
-        $settings = $_POST['settings'];
+        $settings = map_deep( $_POST['settings'], 'sanitize_text_field' );
         update_option( 'membee_login_settings', $settings );
     }
 
@@ -17,7 +19,7 @@
 function render_membee_login_form() {
     $options = get_option( 'membee_login_settings' );
     $title = isset( $options['widget_title'] ) ? $options['widget_title'] : '';
 
-    echo '<h3>' . $title . '</h3>';
+    echo '<h3>' . esc_html( $title ) . '</h3>';
 }

Exploit Outline

The exploit targets the unauthenticated AJAX action 'membee_login_save_settings' (or similar identified via plugin hooks). An attacker sends a POST request to /wp-admin/admin-ajax.php with the action parameter and a 'settings' array containing malicious JavaScript within a field that is later rendered (such as a widget title or redirect URL). Since the plugin version 2.3.6 and earlier registers this action via wp_ajax_nopriv_ and lacks authorization checks, the malicious settings are saved to the database. When a site visitor or administrator views a page containing the [membee_login] shortcode or the Membee Login widget, the injected script executes in their browser context.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.