CVE-2026-32533

LatePoint – Calendar Booking Plugin for Appointments and Events <= 5.2.6 - Authenticated (Subscriber+) Insecure Direct Object Reference

mediumAuthorization Bypass Through User-Controlled Key
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
5.2.7
Patched in
11d
Time to patch

Description

The LatePoint – Calendar Booking Plugin for Appointments and Events plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 5.2.6 due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with Subscriber-level access and above, to perform unauthorized actions.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=5.2.6
PublishedMarch 23, 2026
Last updatedApril 2, 2026
Affected pluginlatepoint

What Changed in the Fix

Changes introduced in v5.2.7

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2026-32533 - LatePoint Insecure Direct Object Reference (IDOR) ## 1. Vulnerability Summary The **LatePoint – Calendar Booking Plugin for Appointments and Events** (up to 5.2.6) is vulnerable to an **Insecure Direct Object Reference (IDOR)**. The vulnerability exists in the plug…

Show full research plan

Research Plan: CVE-2026-32533 - LatePoint Insecure Direct Object Reference (IDOR)

1. Vulnerability Summary

The LatePoint – Calendar Booking Plugin for Appointments and Events (up to 5.2.6) is vulnerable to an Insecure Direct Object Reference (IDOR). The vulnerability exists in the plugin's handling of specific AJAX routes where user-controlled identifiers (keys/IDs) are processed without verifying that the requesting authenticated user (Subscriber level or higher) has ownership or authorization for the target object.

According to the CVSS vector (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N), the impact is limited to Integrity (Low) with no Confidentiality impact. This strongly suggests the vulnerability allows an attacker to modify or delete/cancel objects (like appointments or customer metadata) belonging to other users, rather than viewing private data.

2. Attack Vector Analysis

  • Endpoint: wp-admin/admin-ajax.php
  • AJAX Action: latepoint_route_call (The primary router for the plugin's internal MVC architecture).
  • Vulnerable Parameters:
    • route: Likely appointments__cancel or appointments__delete.
    • params[id]: The ID of the appointment to be cancelled/deleted.
  • Authentication: Subscriber+ (Customer role in LatePoint).
  • Preconditions:
    • Plugin must be active.
    • An appointment belonging to another user (the victim) must exist.
    • The attacker must know or guess the integer ID of the victim's appointment.

3. Code Flow (Inferred)

  1. Entry Point: The client sends an AJAX request to admin-ajax.php with action=latepoint_route_call.
  2. Routing: The main plugin file (or a router class like LatePoint\Helpers\RouterHelper) receives the request.
  3. Action Execution: The router parses the route parameter (e.g., appointments__cancel). This maps to the cancel method in the Appointments controller.
  4. The Sink: Inside the controller (e.g., lib/controllers/appointments_controller.php), the code retrieves the appointment object using params['id'].
  5. The Flaw: The code checks if the user is logged in (Authentication) but fails to check if the customer_id associated with the fetched appointment matches the current_user_id (Authorization).
  6. The Action: The plugin proceeds to update the appointment status to "cancelled" or deletes it from the database.

4. Nonce Acquisition Strategy

LatePoint uses nonces for AJAX requests, typically localized in a JavaScript object.

  1. Create a Trigger Page: LatePoint usually enqueues its assets on pages containing the booking button or customer dashboard.
    • Command: wp post create --post_type=page --post_status=publish --post_title="Dashboard" --post_content='[latepoint_customer_dashboard]'
  2. Navigate and Extract:
    • Login as a Subscriber (Attacker).
    • Navigate to the created page.
    • LatePoint nonces are often found in the latepoint_helper or latepoint_booking_form_data global JS objects.
    • JS Variable: latepoint_helper.nonce or latepoint_booking_form_data.nonce.
  3. Browser Eval:
    • browser_eval("window.latepoint_helper?.nonce")

5. Exploitation Strategy

The goal is to cancel an appointment belonging to another user.

Step 1: Identify Victim Appointment

For the PoC, we will create a Victim user and an appointment for them using WP-CLI. Note the ID of this appointment.

Step 2: Authenticate as Attacker

Login to the WordPress instance as a Subscriber-level user.

Step 3: Perform Unauthorized Cancellation

Send an AJAX request to cancel the victim's appointment.

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=latepoint_route_call
    &route=appointments__cancel
    &latepoint_nonce=<NONCE_OBTAINED_FROM_STEP_4>
    &params[id]=<VICTIM_APPOINTMENT_ID>
    &return_format=json
    

6. Test Data Setup

  1. Victim User:
    • wp user create victim victim@example.com --role=subscriber --user_pass=password
  2. Attacker User:
    • wp user create attacker attacker@example.com --role=subscriber --user_pass=password
  3. LatePoint Setup (Simplified):
    • Ensure at least one Service and one Agent exist (required for appointments).
    • wp latepoint create_service --name="Test Service" (Note: requires actual latepoint CLI or manual DB insertion if CLI is unavailable).
  4. Victim Appointment:
    • Create an appointment manually or via WP-CLI/SQL for the victim user.
    • wp db query "INSERT INTO wp_latepoint_appointments (customer_id, service_id, agent_id, start_date, start_time, end_time, status) VALUES (<VICTIM_ID>, 1, 1, '2026-12-25', 600, 660, 'approved')"
    • Identify the ID of this inserted row.

7. Expected Results

  • Response: The server returns a JSON success message (e.g., {"status": "success", "message": "Appointment cancelled"}).
  • Side Effect: The appointment in the database with the Victim's ID now has a status of cancelled.

8. Verification Steps

  1. Check the status of the appointment using WP-CLI:
    • wp db query "SELECT status FROM wp_latepoint_appointments WHERE id = <VICTIM_APPOINTMENT_ID>"
  2. Confirm that the status has changed from approved to cancelled.
  3. Verify that the Attacker does NOT own this appointment:
    • wp db query "SELECT customer_id FROM wp_latepoint_appointments WHERE id = <VICTIM_APPOINTMENT_ID>" (Should return the Victim's ID, not the Attacker's).

9. Alternative Approaches

If appointments__cancel is properly protected, check other common LatePoint routes for similar IDOR patterns:

  • Route: customers__update_profile
    • Payload: params[id]=<VICTIM_CUSTOMER_ID>&params[first_name]=Hacked
    • Check if the Attacker can change the name/email of the Victim customer.
  • Route: appointments__delete
    • Similar to cancel, but attempts actual removal.
  • Route: activities__get_latest
    • Check if params[customer_id] allows viewing activity logs of other users (unlikely given CVSS "C:N", but worth checking).
Research Findings
Static analysis — not yet PoC-verified

Summary

The LatePoint plugin for WordPress is vulnerable to an Insecure Direct Object Reference (IDOR) and Mass Assignment because it fails to verify ownership of booking objects and restrict updateable fields. Authenticated attackers with Subscriber-level access can manipulate booking IDs to access or cancel appointments belonging to other users, or modify sensitive user profile data.

Vulnerable Code

// lib/helpers/steps_helper.php (v5.2.6)
public static function load_step( $step_code, $format = 'json', $params = [] ) {
    self::$params = $params;

    $step_code = self::check_step_code_access( $step_code );
    // ... No ownership check on params['booking']['id'] before processing ...

---

// lib/controllers/customers_controller.php (v5.2.6)
public function update() {
    $this->check_nonce( 'edit_customer' );
    $customer = new OsCustomerModel( $this->params['id'] );
    if ( $customer->is_new_record() ) {
        // ... error handling ...
    } else {
        $old_customer_data = $customer->get_data_vars();
        $customer->set_data( $this->params['customer'] );
        if ( $customer->save() ) {
            // ... success handling ...
        }
    }
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.2.6/lib/controllers/customers_controller.php /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.2.7/lib/controllers/customers_controller.php
--- /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.2.6/lib/controllers/customers_controller.php	2025-10-02 07:57:50.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.2.7/lib/controllers/customers_controller.php	2026-02-03 06:22:36.000000000 +0000
@@ -149,7 +149,8 @@
 		public function create() {
 			$this->check_nonce( 'new_customer' );
 			$customer = new OsCustomerModel();
-			$customer->set_data( $this->params['customer'] );
+			// Security fix: Prevent mass assignment of wordpress_user_id by non-admin users.
+			$customer->set_data( $this->params['customer'], LATEPOINT_PARAMS_SCOPE_PUBLIC );
 			if ( $customer->save() ) {
 				// translators: %s is the html of a customer edit link
 				$response_html = sprintf( __( 'Customer Created ID: %s', 'latepoint' ), '<span class="os-notification-link" ' . OsCustomerHelper::quick_customer_btn_html( $customer->id ) . '>' . $customer->id . '</span>' );
@@ -178,7 +179,8 @@
 					$status        = LATEPOINT_STATUS_ERROR;
 				} else {
 					$old_customer_data = $customer->get_data_vars();
-					$customer->set_data( $this->params['customer'] );
+					// Security fix: Prevent mass assignment of wordpress_user_id by non-admin users.
+					$customer->set_data( $this->params['customer'], LATEPOINT_PARAMS_SCOPE_PUBLIC );
 					if ( $customer->save() ) {
 						// translators: %s is the html of a customer edit link
 						$response_html = sprintf( __( 'Customer Updated ID: %s', 'latepoint' ), '<span class="os-notification-link" ' . OsCustomerHelper::quick_customer_btn_html( $customer->id ) . '>' . $customer->id . '</span>' );
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.2.6/lib/helpers/steps_helper.php /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.2.7/lib/helpers/steps_helper.php
--- /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.2.6/lib/helpers/steps_helper.php	2025-10-02 07:57:50.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.2.7/lib/helpers/steps_helper.php	2026-02-03 06:22:36.000000000 +0000
@@ -467,6 +467,24 @@
 	public static function load_step( $step_code, $format = 'json', $params = [] ) {
 		self::$params = $params;
 
+		// Security: If loading existing booking by ID, verify ownership.
+		if ( ! empty( $params['booking']['id'] ) ) {
+			$booking_to_check = new OsBookingModel( $params['booking']['id'] );
+			if ( ! $booking_to_check->is_new_record() ) {
+				$current_customer_id = OsAuthHelper::get_logged_in_customer_id();
+				if ( ! $current_customer_id || $booking_to_check->customer_id != $current_customer_id ) {
+					// Unauthorized access - return error.
+					wp_send_json(
+						array(
+							'status' => LATEPOINT_STATUS_ERROR,
+							'message' => __( 'Not Allowed', 'latepoint' ),
+						)
+					);
+					return;
+				}
+			}
+		}
+
 		$step_code = self::check_step_code_access( $step_code );

Exploit Outline

The exploit targets the `latepoint_route_call` AJAX action. An authenticated attacker (Subscriber/Customer) identifies the ID of a victim's booking or customer profile. By sending a POST request to `wp-admin/admin-ajax.php` with `action=latepoint_route_call` and specific parameters, the attacker can manipulate these objects. For IDOR: provide the victim's booking ID in `params[booking][id]` during a booking management step. For Mass Assignment: provide a victim's customer ID in `params[id]` and include sensitive fields like `wordpress_user_id` in the `params[customer]` array to re-link LatePoint profiles to different WordPress users. The attacker must include a valid LatePoint nonce, which is easily extracted from the customer dashboard's JavaScript global variables.

Check if your site is affected.

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