100,000 WordPress Sites Affected by Privilege Escalation via MCP in AI Engine WordPress Plugin


🌞 Spring Into Summer Challenge: Critical Threats = Critical Rewards. 🌞
🔥 Now through August 4, 2025, earn 2X bounty rewards for all in-scope submissions from our ‘High Threat’ list in software with fewer than 5 million active installs. Submit bold. Earn big!🔥


On May 21st, 2025, our Wordfence Threat Intelligence team identified and began the responsible disclosure process for an Insufficient Authorization to Privilege Escalation via MCP (Model Context Protocol) vulnerability in the AI Engine plugin, which is actively installed on more than 100,000 WordPress websites. This vulnerability can be exploited by authenticated attackers, with subscriber-level access and above, to get full access to the MCP and execute various commands like ‘wp_update_user’, allowing them to escalate their privileges to administrators by updating their user role. Please note that this vulnerability only critically affects users who have enabled the Dev Tools and then the MCP module in the settings, which is disabled by default.

Wordfence Premium, Wordfence Care, and Wordfence Response users received a firewall rule to protect against any exploits targeting this vulnerability on May 22, 2025. Sites using the free version of Wordfence will receive the same protection 30 days later on June 21, 2025.

We contacted Jordy Meow on May 21, 2025, and received a response within an hour. After providing full disclosure details, the developer released the patch on June 18, 2025. We would like to commend Jordy Meow for their prompt response and timely patch.

We urge users to update their sites with the latest patched version of AI Engine, version 2.8.4 at the time of this writing, as soon as possible.

Vulnerability Summary from Wordfence Intelligence

CVSS Rating
8.8 (High)
Affected Versions
2.8.0 – 2.8.3
Patched Version
2.8.4
Affected Software
Affected Software Slug

The AI Engine plugin for WordPress is vulnerable to unauthorized modification of data and loss of data due to a missing capability check on the ‘Meow_MWAI_Labs_MCP::can_access_mcp’ function in versions 2.8.0 to 2.8.3. This makes it possible for authenticated attackers, with subscriber-level access and above, to have full access to the MCP and run various commands like ‘wp_create_user’, ‘wp_update_user’ and ‘wp_update_option’, which can be used for privilege escalation, and ‘wp_update_post’, ‘wp_delete_post’, ‘wp_update_comment’ and ‘wp_delete_comment’, which can be used to edit and delete posts and comments.

Technical Analysis

AI Engine is a WordPress plugin that recently introduced support for MCP (Model Context Protocol), which allows AI agents – such as Claude or ChatGPT – to control and manage the WordPress website by executing various commands, managing media files, editing users, and performing complex tasks more reliably than through standard APIs.

Examining the code reveals that the plugin uses the can_access_mcp() function in the Meow_MWAI_Labs_MCP class to perform the permission check to the MCP endpoints. Initially, access is granted to all logged-in users. Additionally, the ‘mwai_allow_mcp‘ filter is used to add custom authentication methods.

class Meow_MWAI_Labs_MCP {
  private $core = null;
  private $namespace = 'mcp/v1';
  private $server_version = '0.0.1';
  private $protocol_version = '2025-03-26';
  private $queue_key = 'mwai_mcp_msg';
  private $session_id = null;
  private $logging = false;
  private $last_action_time = 0;
  private $bearer_token = null;

  #region Initialize
  public function __construct( $core ) {
    $this->core = $core;
    add_action( 'rest_api_init', [ $this, 'rest_api_init' ] );
  }

  public function rest_api_init() {
		$this->bearer_token = $this->core->get_option( 'mcp_bearer_token' );
		if ( !empty( $this->bearer_token ) ) {
			add_filter( 'mwai_allow_mcp', [ $this, 'auth_via_bearer_token' ], 10, 2 );
		}
    register_rest_route( $this->namespace, '/sse', [
      'methods' => 'GET',
      'callback' => [ $this, 'handle_sse' ],
      'permission_callback' => function( $request ) {
				return $this->can_access_mcp( $request );
			},
    ] );

    register_rest_route( $this->namespace, '/sse', [
      'methods' => 'POST',
      'callback' => [ $this, 'handle_sse' ],
      'permission_callback' => function( $request ) {
				return $this->can_access_mcp( $request );
			},
    ] );

    register_rest_route( $this->namespace, '/messages', [
      'methods' => 'POST',
      'callback' => [ $this, 'handle_message' ],
      'permission_callback' => function( $request ) {
				return $this->can_access_mcp( $request );
			},
    ] );
  }
  #endregion

  #region Auth (Bearer token)
  function can_access_mcp( $request ) {
    $logged_in = is_user_logged_in();
    return apply_filters( 'mwai_allow_mcp', $logged_in, $request );
	}

  public function auth_via_bearer_token( $allow, $request ) {
    if ( empty( $this->bearer_token ) ) {
      return false;
    }
		$hdr = $request->get_header( 'authorization' );
    if ( $hdr && preg_match( '/Bearers+(.+)/i', $hdr, $m ) &&
        hash_equals( $this->bearer_token, trim( $m[1] ) ) ) {
      if ( $admin = $this->core->get_admin_user() ) {
        wp_set_current_user( $admin->ID, $admin->user_login );
      }
      return true;
    }
    // ?token=xyz fallback (optional)
    $q = sanitize_text_field( $request->get_param( 'token' ) );
    if ( $q && hash_equals( $this->bearer_token, $q ) ) return true;
    return $allow;
	}

The plugin supports Bearer Token authentication. When the token is configured in the settings, the auth_via_bearer_token() function is integrated into the ‘mwai_allow_mcp‘ filter for authentication checks. Unfortunately, the check is incomplete because it only compares the provided token (from the authorization header or token parameter) against the plugin’s configured token, if the provided token value is set and not empty. The plugin does not check if the value is empty. This means that if the attacker doesn’t specify a token, the default $allow value is returned. Consequently, as is_user_logged_in() determines this value, it defaults to true for all logged-in users.

To summarize, the MCP is accessible to all logged-in users by default, if no authentication method is configured in the plugin, or added via a filter. But even when the Bearer Token authentication method is configured in the plugin settings, this authentication can be bypassed due to the missing empty value check, effectively falling back to the default access for logged-in users. This means that in all instances where a Bearer authentication method is specified or not, an attacker can interact with the MCP.

An attacker who gains access to the MCP endpoint can execute various commands, such as ‘wp_update_user‘, which allows them to update their own user role to administrator, thereby granting themselves elevated privileges.

As with any Privilege Escalation vulnerability, this can be used for complete site compromise. Once an attacker has gained administrative user access to a WordPress site, they can then manipulate anything on the targeted site as a normal administrator would. This includes the ability to upload plugin and theme files, which can be malicious zip files containing backdoors, and modify posts and pages which can be leveraged to redirect site users to other malicious sites or inject spam content.

We would like to draw attention once again to the fact that the vulnerability only critically affects users who have enabled the Dev Tools and then the MCP module in the settings, which is disabled by default.

The Patch

The vendor patched this issue by modifying the can_access_mcp() function to include an administrator capability check. This means that, by default, only administrators can access the MCP endpoints.

function can_access_mcp( $request ) {
  // Default to requiring administrator capability for security
  $is_admin = current_user_can( 'administrator' );
  return apply_filters( 'mwai_allow_mcp', $is_admin, $request );
  }

Additionally, the auth_via_bearer_token() function is also updated to include multiple empty value checks.

public function auth_via_bearer_token( $allow, $request ) {
  // Skip if already authenticated as admin
  if ( $allow ) {
    return $allow;
  }

  $hdr = $request->get_header( 'authorization' );

  // If no authorization header but bearer token is configured, deny access
  if ( !$hdr && !empty( $this->bearer_token ) ) {
    if ( $this->logging ) {
      error_log( '[MCP] ❌ No authorization header provided.' );
    }
    return false;
  }

  // Check for Bearer token in header
  if ( $hdr && preg_match( '/Bearers+(.+)/i', $hdr, $m ) ) {
    $token = trim( $m[1] );
    $auth_result = 'none';

    // Check if it's an OAuth token
    if ( $this->oauth ) {
      $token_data = $this->oauth->validate_token( $token );
      if ( $token_data ) {
        // Set current user based on OAuth token
        wp_set_current_user( $token_data['user_id'] );
        $auth_result = 'oauth';
        // Only log auth for SSE endpoint
        if ( $this->logging && strpos( $request->get_route(), '/sse' ) !== false ) {
          error_log( '[MCP] 🔐 OAuth OK (user: ' . $token_data['user_id'] . ')' );
        }
        return true;
      }
    }

    // Fall back to static bearer token if configured
    if ( !empty( $this->bearer_token ) && hash_equals( $this->bearer_token, $token ) ) {
      if ( $admin = $this->core->get_admin_user() ) {
        wp_set_current_user( $admin->ID, $admin->user_login );
      }
      $auth_result = 'static';
      // Only log auth for SSE endpoint
      if ( $this->logging && strpos( $request->get_route(), '/sse' ) !== false ) {
        error_log( '[MCP] 🔐 Auth OK' );
      }
      return true;
    }

    if ( $this->logging && $auth_result === 'none' ) {
      error_log( '[MCP] ❌ Bearer token invalid.' );
    }
    // Explicitly deny access for invalid tokens
    return false;
  }

  // ?token=xyz fallback (optional) - only for static bearer token
  if ( !empty( $this->bearer_token ) ) {
    $q = sanitize_text_field( $request->get_param( 'token' ) );
    if ( $q && hash_equals( $this->bearer_token, $q ) ) {
      if ( $admin = $this->core->get_admin_user() ) {
        wp_set_current_user( $admin->ID, $admin->user_login );
      }
      return true;
    }
  }

  // If bearer token is configured but no valid auth provided, deny access
  if ( !empty( $this->bearer_token ) ) {
    return false;
  }

  return $allow;
  }

Wordfence Firewall

The following graphic demonstrates the steps to exploitation an attacker might take and at which point the Wordfence firewall would block an attacker from successfully exploiting the vulnerability.

The Wordfence firewall rule detects the malicious REST API action and blocks the request if it does not come from an existing authorized administrator.

Disclosure Timeline

May 21, 2025 – Wordfence Threat Intelligence team discovered the Insufficient Authorization to Privilege Escalation via MCP vulnerability in the AI Engine plugin.
May 21, 2025 – We initiated contact with the plugin vendor asking them to confirm the inbox for handling the discussion.
May 21, 2025 – The vendor confirmed the inbox for handling the discussion.
May 21, 2025 – We sent over the full disclosure details to the vendor. The vendor acknowledged the report and began working on a fix.
May 22, 2025Wordfence Premium, Care, and Response users received a firewall rule to provide protection against any exploits that may target this vulnerability.
June 18, 2025 – The fully patched version of the plugin, 2.8.4, was released.
June 21, 2025 – Wordfence Free users will receive the same protection.

Conclusion

In this blog post, we detailed an Insufficient Authorization to Privilege Escalation via MCP vulnerability within the AI Engine plugin affecting versions 2.8.0 through 2.8.3. This vulnerability allows threat actors with subscriber-level access or higher to get full access to the MCP and gain elevated privileges. The vulnerability has been fully addressed in version 2.8.4 of the plugin.

We encourage WordPress users to verify that their sites are updated to the latest patched version of AI Engine as soon as possible considering the critical nature of this vulnerability.

Wordfence Premium, Wordfence Care, and Wordfence Response users received a firewall rule to protect against any exploits targeting this vulnerability on May 22, 2025. Sites using the free version of Wordfence will receive the same protection 30 days later on June 21, 2025.

If you know someone who uses this plugin on their site, we recommend sharing this advisory with them to ensure their site remains secure, as this vulnerability poses a significant risk.

The post 100,000 WordPress Sites Affected by Privilege Escalation via MCP in AI Engine WordPress Plugin appeared first on Wordfence.

Leave a Comment