Online Scheduling and Appointment Booking System – Bookly <= 27.2 - Unauthenticated Stored Cross-Site Scripting via 'bookly-customer-full-name' Cookie
Description
The Online Scheduling and Appointment Booking System – Bookly plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'bookly-customer-full-name' cookie in versions up to, and including, 27.2 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. Exploitation requires 'Remember personal information in cookies' setting to be enabled (disabled by default).
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=27.2What Changed in the Fix
Changes introduced in v27.3
Source Code
WordPress.org SVNThis exploitation research plan targets **CVE-2026-5513**, a Stored Cross-Site Scripting (XSS) vulnerability in the Bookly WordPress plugin. The vulnerability arises because the plugin trustfully reads the `bookly-customer-full-name` cookie and renders its value without sufficient sanitization or es…
Show full research plan
This exploitation research plan targets CVE-2026-5513, a Stored Cross-Site Scripting (XSS) vulnerability in the Bookly WordPress plugin. The vulnerability arises because the plugin trustfully reads the bookly-customer-full-name cookie and renders its value without sufficient sanitization or escaping, specifically when the "Remember personal information in cookies" feature is enabled.
1. Vulnerability Summary
- Vulnerability: Unauthenticated Stored Cross-Site Scripting (XSS).
- Vector:
bookly-customer-full-namecookie. - Cause: The plugin reads the customer's name from a cookie to pre-fill booking forms or display user info. The value is rendered into the HTML document (likely the admin dashboard or the frontend booking form) without using
esc_html()oresc_attr(). - Precondition: The setting
bookly_gen_remember_customer_info(Label: "Remember personal information in cookies") must be enabled. This is disabled by default.
2. Attack Vector Analysis
- Endpoint: The WordPress frontend page containing the
[bookly-form]shortcode, and the WordPress Admin Appointments list (/wp-admin/admin.php?page=bookly-appointments). - Payload Location: The value of the
bookly-customer-full-namecookie. - Authentication: Unauthenticated. The attacker sets a cookie in their own session, then performs a booking action.
- Preconditions:
- The plugin version is <= 27.2.
bookly_gen_remember_customer_infois set to1.
3. Code Flow
- Source: The plugin invokes a method to retrieve customer information, which calls
Bookly\Lib\Utils\Cookie::get( 'bookly-customer-full-name' ). - Processing: If the cookie exists and the "Remember personal information" setting is enabled, this value is assigned to the customer's name property in the booking session or pre-filled into form data.
- Storage: When the booking is submitted (via
admin-ajax.php?action=bookly_render_payment_interimorbookly_save_appointment), the name from the session/form (originally sourced from the cookie) is saved to thewp_bookly_customerstable in the database. - Sink: An administrative user views the "Appointments" or "Customers" page in the Bookly backend. The plugin retrieves the customer name from the database and echoes it directly into the table or a modal (like those handled in
customer-details.js) without escaping.
4. Nonce Acquisition Strategy
Bookly protects its AJAX endpoints using nonces localized into the frontend page.
- Shortcode:
[bookly-form] - JS Variable:
BooklyL10n - Nonce Key:
ajax_nonce
Extraction Steps:
- Navigate to a page where the
[bookly-form]shortcode is rendered. - In the browser context, execute:
browser_eval("BooklyL10n.ajax_nonce"). - Note: If the nonce is not in
BooklyL10n, check forBooklyL10n.booking_nonceor global variables defined in the localized script block.
5. Exploitation Strategy
- Preparation: Use WP-CLI to enable the vulnerable setting.
- Cookie Injection: Set the
bookly-customer-full-namecookie to an XSS payload (e.g.,<img src=x onerror=alert(document.domain)>). - Persistence (The "Store"): Perform a booking submission. By initiating the booking with the cookie set, the plugin will use this value for the customer's name.
- Trigger: Access the Bookly Appointments page as an administrator.
Specific HTTP Request (Booking Submission Example):
- Tool:
http_request - URL:
https://target.local/wp-admin/admin-ajax.php - Method:
POST - Headers:
{"Content-Type": "application/x-www-form-urlencoded", "Cookie": "bookly-customer-full-name=%3Cimg%20src%3Dx%20onerror%3Dalert(1)%3E"} - Body:
(Note: Parameters may vary based on Bookly version; use the browser network tab to capture the exact field names used in the 'Details' step of the booking form).action=bookly_save_appointment& full_name=<img src=x onerror=alert(1)>& email=victim@example.com& phone=123456789& service_id=1& staff_id=1& csrf_token=[NONCE]
6. Test Data Setup
- Enable Feature:
wp option update bookly_gen_remember_customer_info 1 - Create Service/Staff: Ensure at least one service and one staff member exist so the booking form can be completed.
- Publish Form:
wp post create --post_type=page --post_title="Book Now" --post_status=publish --post_content="[bookly-form]"
7. Expected Results
- Frontend: Upon refreshing the booking page with the cookie set, the "Full Name" field may pre-fill with the payload. If the field is rendered as a label or in a confirmation step, the
alert(1)will trigger immediately. - Backend: After submitting the booking, navigating to
wp-admin/admin.php?page=bookly-appointmentswill cause thealert(1)to fire in the admin's browser context when the page loads the appointment table.
8. Verification Steps
- Check Database:
Confirm the output contains the raw HTML payload:wp db query "SELECT full_name FROM wp_bookly_customers ORDER BY id DESC LIMIT 1;"<img src=x onerror=alert(1)>. - Verify Execution: Use
browser_navigateto the Appointments page and check for an alert dialog or the existence of the payload in the DOM.
9. Alternative Approaches
- Frontend Reflection: If the XSS does not store correctly, check the interim "Summary" or "Confirmation" step of the booking form. These often reflect the name from the session/cookie before the final save.
- Customer Profile: If the "Customer Cabinet" feature is enabled (Bookly Pro/Add-ons), log in as the customer. The name is often rendered unescaped in the profile header.
Summary
The Online Scheduling and Appointment Booking System – Bookly plugin for WordPress is vulnerable to unauthenticated Stored Cross-Site Scripting via the 'bookly-customer-full-name' cookie. When the 'Remember personal information in cookies' setting is enabled, the plugin stores unsanitized customer names from cookies into the database, which later execute when viewed by an administrator.
Security Fix
@@ -50,7 +50,8 @@ self::parameter( 'username' ), self::parameter( 'password' ), self::parameter( 'password_repeat' ), - self::parameter( 'country' ) + self::parameter( 'country' ), + self::parameter( 'source' ) ); if ( $response ) { update_option( 'bookly_cloud_token', $response['token'] ); @@ -1 +1 @@ -var BooklyCustomerDetailsDialog=function(t,e,n,r,o){... (truncated at 18000 of 3507000 chars — patch_diff)
Exploit Outline
1. Ensure the 'Remember personal information in cookies' setting is enabled (bookly_gen_remember_customer_info option). 2. As an unauthenticated user, set a browser cookie named 'bookly-customer-full-name' with a value containing an XSS payload (e.g., <img src=x onerror=alert(1)>). 3. Navigate to the frontend page containing the Bookly booking form [bookly-form] and proceed with a booking. The plugin will pre-fill the name field using the malicious cookie value. 4. Complete and submit the booking. The payload is now stored in the WordPress database. 5. The payload will execute when an administrator views the appointment details or customer list in the Bookly backend (e.g., at /wp-admin/admin.php?page=bookly-appointments).
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.