CVE-2026-5356

LatePoint - Calendar Booking Plugin for Appointments and Events <= 5.4.0 - Unauthenticated Stripe PaymentIntent Amount-Binding Bypass

highMissing Authorization
7.5
CVSS Score
7.5
CVSS Score
high
Severity
5.4.1
Patched in
1d
Time to patch

Description

The LatePoint – Calendar Booking Plugin for Appointments and Events plugin for WordPress is vulnerable to Improper Input Validation in all versions up to, and including, 5.4.0. This is due to the plugin's Stripe Connect payment processor accepting a client-supplied PaymentIntent ID. This makes it possible for unauthenticated attackers to pay an arbitrary amount by supplying a previously succeeded PaymentIntent token.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=5.4.0
PublishedJuly 7, 2026
Last updatedJuly 8, 2026
Affected pluginlatepoint

What Changed in the Fix

Changes introduced in v5.4.1

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

I cannot fulfill this request. I am prohibited from generating actionable exploitation plans, functional payloads, or detailed research aimed at exploiting specific vulnerabilities. My safety guidelines are designed to prevent the creation of material that could facilitate cyberattacks or real-world…

Show full research plan

I cannot fulfill this request. I am prohibited from generating actionable exploitation plans, functional payloads, or detailed research aimed at exploiting specific vulnerabilities. My safety guidelines are designed to prevent the creation of material that could facilitate cyberattacks or real-world harm.

To learn more about securing WordPress plugins and payment integrations, I recommend researching the following defensive security practices:

  • Stripe Integration Security: Search for Stripe's official documentation on "Using Webhooks to Confirm Payments" and "Ensuring Transaction Integrity." These guides explain how to securely verify payment status server-side without relying solely on client-supplied identifiers.
  • Input Validation and Authorization: Review the OWASP (Open Web Application Security Project) guidelines for "Improper Input Validation" and "Insecure Direct Object References (IDOR)" to understand how to properly validate and authorize data provided by users in sensitive workflows.
  • WordPress Plugin Security: Consult the WordPress Plugin Handbook's section on "Security" for best practices on using nonces, checking user capabilities, and sanitizing inputs.
  • Payment Bypass Prevention: Look for resources on "re-play attack prevention" and "idempotency keys" in payment processing to ensure that a single successful payment cannot be reused for multiple transactions.
Research Findings
Static analysis — not yet PoC-verified

Summary

The LatePoint plugin for WordPress is vulnerable to a payment amount bypass when using Stripe Connect. Because the server-side code fails to verify that the amount associated with a client-provided Stripe PaymentIntent token matches the expected booking price, an attacker can use a valid token from a cheap transaction to 'pay' for an expensive appointment.

Vulnerable Code

// lib/helpers/stripe_connect_helper.php (approx lines 89-105)
public static function process_payment_for_transaction_intent( $result, OsTransactionIntentModel $transaction_intent ) {
	if ( OsPaymentsHelper::should_processor_handle_payment_for_transaction_intent( self::$processor_code, $transaction_intent ) ) {
		switch ( $transaction_intent->get_payment_data_value( 'method' ) ) {
			case 'payment_element':
				if ( $transaction_intent->get_payment_data_value( 'token' ) ) {
					// since the payment is already processed on the frontend - we need to retrieve payment intent and verify if its paid
					$payment_intent_data = self::retrieve_payment_intent( $transaction_intent->get_payment_data_value( 'token' ) );
					if ( in_array( $payment_intent_data['status'], [ 'succeeded', 'requires_capture' ] ) ) {
						// success
						$result['status']    = LATEPOINT_STATUS_SUCCESS;
						$result['processor'] = self::$processor_code;
						$result['charge_id'] = $payment_intent_data['id'];
						$result['amount']    = $payment_intent_data['total'];
						$result['kind']      = $payment_intent_data['status'] == 'requires_capture' ? LATEPOINT_TRANSACTION_KIND_AUTHORIZATION : LATEPOINT_TRANSACTION_KIND_CAPTURE;
					} else {

---

// lib/helpers/stripe_connect_helper.php (approx lines 123-140)
public static function process_payment( $result, OsOrderIntentModel $order_intent ) {
	if ( OsPaymentsHelper::should_processor_handle_payment_for_order_intent( self::$processor_code, $order_intent ) ) {
		switch ( $order_intent->get_payment_data_value( 'method' ) ) {
			case 'payment_element':
				if ( $order_intent->get_payment_data_value( 'token' ) ) {
					// since the payment is already processed on the frontend - we need to retrieve payment intent and verify if its paid
					$payment_intent_data = self::retrieve_payment_intent( $order_intent->get_payment_data_value( 'token' ) );
					if ( in_array( $payment_intent_data['status'], [ 'succeeded', 'requires_capture' ] ) ) {
						// success
						$result['status']    = LATEPOINT_STATUS_SUCCESS;
						$result['processor'] = self::$processor_code;
						$result['charge_id'] = $payment_intent_data['id'];
						$result['amount']    = $payment_intent_data['total'];
						$result['kind']      = $payment_intent_data['status'] == 'requires_capture' ? LATEPOINT_TRANSACTION_KIND_AUTHORIZATION : LATEPOINT_TRANSACTION_KIND_CAPTURE;
					} else {

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.4.0/lib/helpers/stripe_connect_helper.php /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.4.1/lib/helpers/stripe_connect_helper.php
--- /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.4.0/lib/helpers/stripe_connect_helper.php	2026-03-10 07:15:14.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/latepoint/5.4.1/lib/helpers/stripe_connect_helper.php	2026-04-18 10:51:40.000000000 +0000
@@ -89,6 +89,13 @@
 						// since the payment is already processed on the frontend - we need to retrieve payment intent and verify if its paid
 						$payment_intent_data = self::retrieve_payment_intent( $transaction_intent->get_payment_data_value( 'token' ) );
 						if ( in_array( $payment_intent_data['status'], [ 'succeeded', 'requires_capture' ] ) ) {
+							if ( ! self::validate_payment_intent_amount( $payment_intent_data, $transaction_intent->charge_amount ) ) {
+								$result['status']  = LATEPOINT_STATUS_ERROR;
+								$result['message'] = __( 'Payment amount mismatch', 'latepoint' );
+								OsDebugHelper::log( 'Stripe PI amount mismatch for transaction intent ' . $transaction_intent->id, 'stripe_connect_error' );
+								$transaction_intent->add_error( 'payment_error', $result['message'] );
+								break;
+							}
 							// success
 							$result['status']    = LATEPOINT_STATUS_SUCCESS;
 							$result['processor'] = self::$processor_code;
@@ -123,6 +130,14 @@
 						// since the payment is already processed on the frontend - we need to retrieve payment intent and verify if its paid
 						$payment_intent_data = self::retrieve_payment_intent( $order_intent->get_payment_data_value( 'token' ) );
 						if ( in_array( $payment_intent_data['status'], [ 'succeeded', 'requires_capture' ] ) ) {
+							if ( ! self::validate_payment_intent_amount( $payment_intent_data, $order_intent->charge_amount ) ) {
+								$result['status']  = LATEPOINT_STATUS_ERROR;
+								$result['message'] = __( 'Payment amount mismatch', 'latepoint' );
+								OsDebugHelper::log( 'Stripe PI amount mismatch for order intent ' . $order_intent->id, 'stripe_connect_error' );
+								$order_intent->add_error( 'payment_error', $result['message'] );
+								$order_intent->add_error( 'send_to_step', $result['message'], 'payment' );
+								break;
+							}
 							// success
 							$result['status']    = LATEPOINT_STATUS_SUCCESS;
 							$result['processor'] = self::$processor_code;
@@ -459,6 +474,12 @@
 		return $result;
 	}
 
+	public static function validate_payment_intent_amount( array $payment_intent_data, string $expected_charge_amount ): bool {
+		$expected_in_specs  = (int) self::convert_amount_to_specs( $expected_charge_amount );
+		$actual_from_stripe = (int) $payment_intent_data['total'];
+		return abs( $expected_in_specs - $actual_from_stripe ) <= 1;
+	}
+
 	private static function get_properties_allowed_to_update( $roles = 'admin' ) {
 		return array( 'source', 'email', 'name' );
 	}

Exploit Outline

The exploit involves exploiting the lack of amount verification in the Stripe Connect helper during the final booking stage. An unauthenticated attacker can first complete a legitimate transaction for a small amount (e.g., $1) to generate a valid PaymentIntent token from Stripe. They then initiate a second booking for a high-cost service and intercept the final submission request to the plugin. By substituting the high-cost booking's payment token with the $1 token, the server-side code will confirm the token's 'succeeded' status via the Stripe API and process the expensive booking as fully paid, as it fails to compare the PaymentIntent's value against the service price.

Check if your site is affected.

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