CVE-2026-5513

Online Scheduling and Appointment Booking System – Bookly <= 27.2 - Unauthenticated Stored Cross-Site Scripting via 'bookly-customer-full-name' Cookie

highImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
7.2
CVSS Score
7.2
CVSS Score
high
Severity
27.3
Patched in
1d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=27.2
PublishedJune 12, 2026
Last updatedJune 13, 2026

What Changed in the Fix

Changes introduced in v27.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

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 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-name cookie.
  • 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() or esc_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-name cookie.
  • Authentication: Unauthenticated. The attacker sets a cookie in their own session, then performs a booking action.
  • Preconditions:
    1. The plugin version is <= 27.2.
    2. bookly_gen_remember_customer_info is set to 1.

3. Code Flow

  1. Source: The plugin invokes a method to retrieve customer information, which calls Bookly\Lib\Utils\Cookie::get( 'bookly-customer-full-name' ).
  2. 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.
  3. Storage: When the booking is submitted (via admin-ajax.php?action=bookly_render_payment_interim or bookly_save_appointment), the name from the session/form (originally sourced from the cookie) is saved to the wp_bookly_customers table in the database.
  4. 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:

  1. Navigate to a page where the [bookly-form] shortcode is rendered.
  2. In the browser context, execute: browser_eval("BooklyL10n.ajax_nonce").
  3. Note: If the nonce is not in BooklyL10n, check for BooklyL10n.booking_nonce or global variables defined in the localized script block.

5. Exploitation Strategy

  1. Preparation: Use WP-CLI to enable the vulnerable setting.
  2. Cookie Injection: Set the bookly-customer-full-name cookie to an XSS payload (e.g., <img src=x onerror=alert(document.domain)>).
  3. 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.
  4. 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:
    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]
    
    (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).

6. Test Data Setup

  1. Enable Feature: wp option update bookly_gen_remember_customer_info 1
  2. Create Service/Staff: Ensure at least one service and one staff member exist so the booking form can be completed.
  3. 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-appointments will cause the alert(1) to fire in the admin's browser context when the page loads the appointment table.

8. Verification Steps

  1. Check Database:
    wp db query "SELECT full_name FROM wp_bookly_customers ORDER BY id DESC LIMIT 1;"
    
    Confirm the output contains the raw HTML payload: <img src=x onerror=alert(1)>.
  2. Verify Execution: Use browser_navigate to 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.
Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/bookly-responsive-appointment-booking-tool/27.2/backend/components/cloud/account/Ajax.php /home/deploy/wp-safety.org/data/plugin-versions/bookly-responsive-appointment-booking-tool/27.3/backend/components/cloud/account/Ajax.php
--- /home/deploy/wp-safety.org/data/plugin-versions/bookly-responsive-appointment-booking-tool/27.2/backend/components/cloud/account/Ajax.php	2026-03-20 09:05:58.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/bookly-responsive-appointment-booking-tool/27.3/backend/components/cloud/account/Ajax.php	2026-04-13 08:50:54.000000000 +0000
@@ -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'] );
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/bookly-responsive-appointment-booking-tool/27.2/backend/components/dialogs/appointment/customer_details/resources/js/customer-details.js /home/deploy/wp-safety.org/data/plugin-versions/bookly-responsive-appointment-booking-tool/27.3/backend/components/dialogs/appointment/customer_details/resources/js/customer-details.js
--- /home/deploy/wp-safety.org/data/plugin-versions/bookly-responsive-appointment-booking-tool/27.2/backend/components/dialogs/appointment/customer_details/resources/js/customer-details.js	2026-03-20 09:05:58.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/bookly-responsive-appointment-booking-tool/27.3/backend/components/dialogs/appointment/customer_details/resources/js/customer-details.js	2026-04-13 08:50:54.000000000 +0000
@@ -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.