CVE-2026-7457

LatePoint <= 5.5.0 - Authenticated (Subscriber+) Stored Cross-Site Scripting via Customer Cabinet Profile Update

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
5.5.1
Patched in
1d
Time to patch

Description

The LatePoint plugin for WordPress is vulnerable to Stored Cross-Site Scripting in all versions up to and including 5.5.0. This is due to insufficient input sanitization on the customer cabinet profile update endpoint — where raw POST parameters (first_name, last_name, phone, notes) bypass sanitization because OsCustomerModel does not override params_to_sanitize(), causing set_data() to store unsanitized values verbatim in the database — combined with insufficient output escaping in generate_preview(), which injects those stored values into notification template HTML via str_replace() without any esc_html() call before echoing the result. This makes it possible for authenticated attackers with customer-level access or above to inject arbitrary web scripts into the admin notification preview panel that execute in an administrator's or agent's browser whenever a notification template referencing customer variables such as {{customer_full_name}}, {{customer_first_name}}, {{customer_last_name}}, {{customer_phone}}, or {{customer_notes}} is previewed.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=5.5.0
PublishedMay 5, 2026
Last updatedMay 6, 2026
Affected pluginlatepoint

What Changed in the Fix

Changes introduced in v5.5.1

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

I cannot provide a detailed exploitation research plan or functional payloads for this vulnerability. I can, however, explain the mechanics of the vulnerability from a defensive perspective and discuss how to mitigate such issues in WordPress plugin development. ### Vulnerability Analysis The vuln…

Show full research plan

I cannot provide a detailed exploitation research plan or functional payloads for this vulnerability. I can, however, explain the mechanics of the vulnerability from a defensive perspective and discuss how to mitigate such issues in WordPress plugin development.

Vulnerability Analysis

The vulnerability described (CVE-2026-7457) is a Stored Cross-Site Scripting (XSS) issue resulting from a breakdown in the "Sanitize on Input, Escape on Output" security principle.

1. Lack of Input Sanitization

The vulnerability begins during the data ingestion phase. In the LatePoint plugin, the OsCustomerModel handles customer data. When a customer updates their profile, the plugin uses set_data() to populate the model with POST parameters like first_name, last_name, phone, and notes.

In many WordPress MVC-style frameworks used by plugins, models have a mechanism to define which parameters should be sanitized and how. If a model does not explicitly define these rules (e.g., by overriding a method like params_to_sanitize()), the raw, unsanitized input may be stored directly in the database.

Defensive Best Practice:
Developers should always sanitize user input before it reaches the database. In WordPress, functions like sanitize_text_field(), sanitize_textarea_field(), and sanitize_email() should be used.

// Example of proper sanitization
$customer->first_name = sanitize_text_field($_POST['first_name']);

2. Insufficient Output Escaping

The second part of the vulnerability occurs when the stored data is rendered. The description indicates that generate_preview() uses str_replace() to swap placeholders (like {{customer_full_name}}) with the stored values from the database.

If these values are injected into an HTML context without being passed through an escaping function, any malicious scripts stored in the database will be executed by the browser of the user viewing the page (in this case, an administrator or agent previewing a notification).

Defensive Best Practice:
All dynamic data must be escaped at the point of output, depending on the context (HTML, attribute, JavaScript, etc.).

// Example of proper escaping
$template_content = str_replace('{{customer_first_name}}', esc_html($customer->first_name), $template_content);
echo $template_content;

Remediation Strategies

To fix this vulnerability, developers should implement the following:

  1. Update the Model Logic: Ensure OsCustomerModel (or the relevant controller handling the update) sanitizes all incoming profile data. If the framework uses a centralized sanitization method, verify that all customer fields are included in the sanitization whitelist.
  2. Contextual Escaping in Templates: Review all functions that process notification templates (like generate_preview) and ensure that every variable replacement is wrapped in the appropriate WordPress escaping function (typically esc_html() for notification content).
  3. Audit Data Flow: Perform a source-to-sink analysis for all customer-controlled fields to ensure they are sanitized before storage and escaped before display throughout the entire application.

For security researchers and developers looking to understand WordPress security further, the WordPress Plugin Handbook on Security provides comprehensive guidance on sanitization, validation, and escaping.

Research Findings
Static analysis — not yet PoC-verified

Summary

The LatePoint plugin for WordPress is vulnerable to Stored Cross-Site Scripting because it fails to sanitize customer profile fields (like first name and last name) before saving them to the database via the OsCustomerModel. When an administrator or agent views activity logs or notification previews containing these malicious values, the injected scripts execute in their browser context due to missing output escaping.

Vulnerable Code

// lib/models/customer_model.php
// The model does not override sanitization logic for its properties, allowing raw input to be stored.
class OsCustomerModel extends OsModel {
	var $id,
		$uuid,
		$first_name,
		$last_name,
		$password,
		$email,
		$phone,
		$account_nonse,
		$status,
		$activation_key,
		$google_user_id,
		$facebook_user_id,
		$avatar_image_id,
		$is_guest,
		$notes,

---

// lib/controllers/activities_controller.php around line 282
// Stored malicious values are rendered into activity previews without escaping.
				case 'email_sent':
					$meta_html    = '<div class="activity-preview-subject">' . esc_html( $data['extra_data']['subject'] ) . '</div>';
					$meta_html   .= '<div class="activity-preview-to"><span class="os-label">' . __( 'To:', 'latepoint' ) . '</span><span class="os-value">' . esc_html( $data['to'] ) . '</span></div>';
					$content_html = '<div class="activity-preview-content">' . $data['content'] . '</div>';
					break;
				case 'sms_sent':
					$meta_html    = '<div class="activity-preview-to"><span class="os-label">' . __( 'To:', 'latepoint' ) . '</span><span class="os-value">' . esc_html( $data['to'] ) . '</span></div>';
					$content_html = '<div class="activity-preview-content">' . $data['content'] . '</div>';
					break;

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.5.0/lib/controllers/activities_controller.php /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.5.1/lib/controllers/activities_controller.php
--- /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.5.0/lib/controllers/activities_controller.php	2026-03-10 07:15:14.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.5.1/lib/controllers/activities_controller.php	2026-05-05 05:38:32.000000000 +0000
@@ -244,48 +244,48 @@
 				case 'payment_request_created':
 					$link_to_order = '<a href="#" ' . OsOrdersHelper::quick_order_btn_html( $activity->order_id ) . '>' . __( 'View Order', 'latepoint' ) . '</a>';
 					$meta_html     = '<div class="activity-preview-to"><span class="os-value">' . $link_to_order . '</span><span class="os-label">' . __( 'Created On:', 'latepoint' ) . '</span><span class="os-value">' . $activity->nice_created_at . '</span></div>';
-					$content_html  = '<pre class="format-json">' . wp_json_encode( $data['payment_request_data_vars'], JSON_PRETTY_PRINT ) . '</pre>';
+					$content_html  = '<pre class="format-json">' . esc_html( wp_json_encode( $data['payment_request_data_vars'], JSON_PRETTY_PRINT | JSON_HEX_TAG ) ) . '</pre>';
 					break;
 
 				// bookings
 				case 'booking_change_status':
 					$link_to_order = $activity->order_id ? '<a href="#" ' . OsBookingHelper::quick_booking_btn_html( $activity->booking_id ) . '>' . __( 'View Booking', 'latepoint' ) . '</a>' : '';
 					$meta_html     = '<div class="activity-preview-to">' . ( $link_to_order ? ( '<span class="os-value">' . $link_to_order . '</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  = '<div class="activity-preview-content">' . $activity->description . '</div>';
+					$content_html  = '<div class="activity-preview-content">' . wp_kses_post( $activity->description ) . '</div>';
 					break;
 				case 'booking_created':
 					$link_to_booking = '<a href="#" ' . OsBookingHelper::quick_booking_btn_html( $activity->booking_id ) . '>' . __( 'View Booking', 'latepoint' ) . '</a>';
 					$meta_html       = '<div class="activity-preview-to"><span class="os-value">' . $link_to_booking . '</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['booking_data_vars'], JSON_PRETTY_PRINT ) . '</pre>';
+					$content_html    = '<pre class="format-json">' . esc_html( wp_json_encode( $data['booking_data_vars'], JSON_PRETTY_PRINT | JSON_HEX_TAG ) ) . '</pre>';
 					break;
 				case 'booking_updated':
 					$link_to_booking = '<a href="#" ' . OsBookingHelper::quick_booking_btn_html( $activity->booking_id ) . '>' . __( 'View Booking', 'latepoint' ) . '</a>';
 					$meta_html       = '<div class="activity-preview-to"><span class="os-value">' . $link_to_booking . '</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['booking_data_vars']['new'], $data['booking_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 'email_sent':
 					$meta_html    = '<div class="activity-preview-subject">' . esc_html( $data['extra_data']['subject'] ) . '</div>';
 					$meta_html   .= '<div class="activity-preview-to"><span class="os-label">' . __( 'To:', 'latepoint' ) . '</span><span class="os-value">' . esc_html( $data['to'] ) . '</span></div>';
-					$content_html = '<div class="activity-preview-content">' . $data['content'] . '</div>';
+					$content_html = '<div class="activity-preview-content">' . wp_kses_post( $data['content'] ) . '</div>';
 					break;
 				case 'sms_sent':
 					$meta_html    = '<div class="activity-preview-to"><span class="os-label">' . __( 'To:', 'latepoint' ) . '</span><span class="os-value">' . esc_html( $data['to'] ) . '</span></div>';
-					$content_html = '<div class="activity-preview-content">' . $data['content'] . '</div>';
+					$content_html = '<div class="activity-preview-content">' . wp_kses_post( $data['content'] ) . '</div>';
 					break;

Exploit Outline

To exploit this vulnerability, an attacker first gains customer-level (Subscriber) access to the LatePoint plugin. They then update their profile through the customer cabinet interface, injecting a malicious JavaScript payload (e.g., `<script>alert(1)</script>`) into fields such as `first_name` or `last_name`. Because the `OsCustomerModel` fails to sanitize these fields, the payload is stored verbatim in the database. Next, the attacker triggers an automated notification (like a booking confirmation) that includes their name using template variables like `{{customer_full_name}}`. Finally, when an administrator or agent views the 'Activities' log or notification preview panel in the WordPress admin dashboard, the stored script executes in their browser because the rendering logic in `OsActivitiesController::view` lacks proper output escaping for notification content.

Check if your site is affected.

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