CVE-2026-1704

Appointment Booking Calendar <= 1.6.9.29 - Insecure Direct Object Reference to Authenticated (Staff+) Sensitive Information Exposure

mediumAuthorization Bypass Through User-Controlled Key
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
1.6.10.0
Patched in
1d
Time to patch

Description

The Appointment Booking Calendar — Simply Schedule Appointments Booking Plugin plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 1.6.9.29. This is due to the `get_item_permissions_check` method granting access to users with the `ssa_manage_appointments` capability without validating staff ownership of the requested appointment. This makes it possible for authenticated attackers, with custom-level access and above (users granted the ssa_manage_appointments capability, such as Team Members), to view appointment records belonging to other staff members and access sensitive customer personally identifiable information via the appointment ID parameter.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.6.9.29
PublishedMarch 12, 2026
Last updatedMarch 13, 2026

What Changed in the Fix

Changes introduced in v1.6.10.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This plan outlines the research and exploitation process for **CVE-2026-1704**, an Insecure Direct Object Reference (IDOR) vulnerability in the "Simply Schedule Appointments" plugin. ### 1. Vulnerability Summary The vulnerability exists in the plugin's REST API implementation for retrieving appoint…

Show full research plan

This plan outlines the research and exploitation process for CVE-2026-1704, an Insecure Direct Object Reference (IDOR) vulnerability in the "Simply Schedule Appointments" plugin.

1. Vulnerability Summary

The vulnerability exists in the plugin's REST API implementation for retrieving appointment details. Specifically, the get_item_permissions_check method (likely within SSA_Appointments_Api, inferred from plugin structure) grants access to any user with the ssa_manage_appointments capability.

While this capability is intended for staff/team members, the implementation fails to verify if the requesting staff member is actually assigned to or owns the specific appointment requested via the id parameter. This allows a low-privileged "Team Member" to view sensitive customer Personally Identifiable Information (PII) for appointments belonging to any other staff member.

2. Attack Vector Analysis

  • Endpoint: /wp-json/ssa/v1/appointments/(?P<id>\d+) (Inferred based on SSA_Settings_Api patterns in includes/class-settings-api.php).
  • HTTP Method: GET
  • Vulnerable Parameter: id (The appointment ID).
  • Authentication Required: Authenticated user with the ssa_manage_appointments capability.
  • Required Role: Typically a user with a "Team Member" role or a custom role granted SSA capabilities.
  • Precondition: The "Team Booking" feature must be enabled or the system must have multiple staff members and existing appointments.

3. Code Flow

  1. Request Entry: The attacker sends a GET request to /wp-json/ssa/v1/appointments/{id}.
  2. Routing: WordPress matches the route registered via register_rest_route.
  3. Permission Check: The controller calls get_item_permissions_check($request).
  4. Flawed Logic: The method checks current_user_can('ssa_manage_appointments'). If true, it returns true. It does not query the SSA_Appointment_Model to verify if the $request['id'] is associated with the current_user_id.
  5. Data Retrieval: The controller calls get_item( $request ), which invokes SSA_Appointment_Model::db_get( $id ).
  6. Information Exposure: The response returns the full appointment record, including the customer_information array containing PII (name, email, phone, etc.).

4. Nonce Acquisition Strategy

SSA uses the standard WordPress REST API nonce (wp_rest). This nonce is required for all authenticated ssa/v1 requests.

  1. Identify Trigger: The SSA Admin dashboard (/wp-admin/admin.php?page=ssa-settings) loads the main booking application which enqueues the necessary scripts and nonces.
  2. Setup Page: A page containing the SSA admin app is needed. Usually, simply logging in as a user with ssa_manage_appointments and visiting the SSA dashboard is sufficient.
  3. Navigation: Use browser_navigate to target_url + "/wp-admin/admin.php?page=ssa-settings".
  4. Extraction: SSA localizes its data into a global JavaScript object. Based on common plugin patterns:
    • Variable: window.SSA_Data or window.ssa_admin.
    • Command: browser_eval("window.SSA_Data?.rest_nonce || window.ssa_admin?.nonce").
    • Alternative: Extract the standard wp-json nonce from the wp-api-fetch initialization or the _wpnonce in the page source.

5. Test Data Setup

To demonstrate the vulnerability, we need two staff members and one appointment.

  1. Create Staff 1 (Victim):
    wp user create victim victim@example.com --role=author --user_pass=password
    # Grant SSA capability (usually handled by the plugin, but we can force it)
    wp cap add author ssa_manage_appointments
    
  2. Create Staff 2 (Attacker):
    wp user create attacker attacker@example.com --role=author --user_pass=password
    wp cap add author ssa_manage_appointments
    
  3. Create Appointment for Victim:
    Use wp eval to insert directly into the SSA appointments table (table name prefix: wp_ssa_appointments).
    $plugin = Simply_Schedule_Appointments::get_instance();
    $appointment_id = $plugin->appointment_model->insert(array(
        'appointment_type_id' => 1,
        'customer_information' => array(
            'first_name' => 'Secret',
            'last_name' => 'Customer',
            'email' => 'sensitive@victim.com',
            'phone' => '555-0199'
        ),
        'status' => 'booked',
        'staff_ids' => array( [ID_OF_VICTIM_USER] )
    ));
    echo "Created Appointment ID: " . $appointment_id;
    

6. Exploitation Strategy

  1. Login: Log in as attacker.
  2. Extract Nonce: Navigate to /wp-admin/admin.php?page=ssa-settings and extract the wp_rest nonce.
  3. Perform IDOR: Use the http_request tool to fetch an appointment ID belonging to the victim.

HTTP Request:

GET /wp-json/ssa/v1/appointments/[VICTIM_APPOINTMENT_ID] HTTP/1.1
Host: localhost:8080
X-WP-Nonce: [EXTRACTED_NONCE]
Cookie: [ATTACKER_COOKIES]

7. Expected Results

  • Vulnerable Version: The server returns a 200 OK response with a JSON body containing the customer_information field with the victim's customer's PII.
  • Patched Version: The server returns a 403 Forbidden or 401 Unauthorized because the requester is not the assigned staff member.

8. Verification Steps

  1. Analyze Response: Inspect the JSON output for keys: first_name, last_name, email, and phone.
  2. Verify Ownership: Use WP-CLI to confirm the appointment belongs to the victim and not the attacker:
    # Check staff_ids for the appointment
    wp db query "SELECT staff_ids FROM wp_ssa_appointments WHERE id = [ID]"
    

9. Alternative Approaches

  • List Appointments: If the single item endpoint is protected, check the collection endpoint: GET /wp-json/ssa/v1/appointments. Does it filter by the current user? If not, it's a "Mass Assignment" or "Information Exposure" vulnerability.
  • Capability Level: Test if lower roles (subscriber) can access the endpoint if the capability check is entirely missing or incorrectly implemented.
  • Meta Model: Check ssa/v1/appointment-meta (inferred from property appointment_meta_model) for similar IDOR vulnerabilities.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Simply Schedule Appointments plugin for WordPress is vulnerable to an Insecure Direct Object Reference (IDOR) because its REST API permission checks fail to verify appointment ownership for staff members. Authenticated attackers with the 'ssa_manage_appointments' capability (such as Team Members) can exploit this to view sensitive customer personally identifiable information (PII) belonging to appointments assigned to other staff members.

Vulnerable Code

// includes/class-appointment-model.php around line 1442

if ( current_user_can( 'ssa_manage_appointments' ) ) {
	return true;
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.9.29/CHANGELOG.md /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.10.0/CHANGELOG.md
--- /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.9.29/CHANGELOG.md	2026-03-05 19:52:38.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.10.0/CHANGELOG.md	2026-03-11 18:09:58.000000000 +0000
@@ -1,5 +1,13 @@
 # Changelog
 
+## SSA-VERSION-PREFIX.6.10.0 - 2026-03-05
+
+### Fixes
+
+- Vulnerability - Insecure Direct Object Reference to Authenticated (Staff+) Sensitive Information Exposure
+- Add Authorization to Settings REST API Endpoint
+- Unauthenticated SQL Injection in Simply Schedule Appointments Plugin
+
 ## SSA-VERSION-PREFIX.6.9.29 - 2026-02-24
 
 ### Fixes
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.9.29/includes/class-appointment-model.php /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.10.0/includes/class-appointment-model.php
--- /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.9.29/includes/class-appointment-model.php	2026-03-05 19:52:38.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.10.0/includes/class-appointment-model.php	2026-03-11 18:09:58.000000000 +0000
@@ -1440,7 +1440,10 @@
 		}
 
 		if ( current_user_can( 'ssa_manage_appointments' ) ) {
-			return true;
+			$params = $request->get_params();
+			if ( ! empty( $params['id'] ) && $this->plugin->staff_appointment_model->user_has_appointment_id( get_current_user_id(), (int) $params['id'] ) ) {
+				return true;
+			}
 		}
 
 		$params = $request->get_params();
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.9.29/includes/class-settings-api.php /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.10.0/includes/class-settings-api.php
--- /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.9.29/includes/class-settings-api.php	2023-01-31 22:17:48.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.10.0/includes/class-settings-api.php	2026-03-11 18:09:58.000000000 +0000
@@ -128,6 +128,7 @@
 	 */
 	public function get_item( $request ) {
 		$settings = $this->plugin->settings->get();
+		$settings = $this->plugin->settings->remove_unauthorized_settings_for_current_user( $settings );
 		if ( empty( $settings[$request['id']] ) ) {
 			return array( 
 				'response_code' => 404,
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.9.29/includes/lib/td-util/class-td-db-model.php /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.10.0/includes/lib/td-util/class-td-db-model.php
--- /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.9.29/includes/lib/td-util/class-td-db-model.php	2026-03-05 19:52:38.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.10.0/includes/lib/td-util/class-td-db-model.php	2026-03-11 18:09:58.000000000 +0000
@@ -1181,7 +1181,15 @@
 		$sanitized_orderby = sanitize_key(esc_sql( $args['orderby'] ));
 		$sanitized_order = 'ASC' === strtoupper( esc_sql( $args['order'] ) ) ? 'ASC' : 'DESC';
 		$table_name      = $this->get_table_name();
-		$fields          = empty( $args['fields'] ) ? '*' : '`' . implode( '`, `', $args['fields'] ) . '`';
+
+		if ( empty( $args['fields'] ) ) {
+			$fields = '*';
+		} else {
+			$valid_fields   = array_keys( $this->get_fields() );
+			$requested      = array_map( 'sanitize_key', (array) $args['fields'] );
+			$safe_fields    = array_intersect( $requested, $valid_fields );
+			$fields         = empty( $safe_fields ) ? '*' : '`' . implode( '`, `', $safe_fields ) . '`';
+		}

Exploit Outline

1. Authenticate as a user with the 'ssa_manage_appointments' capability (typically a Team Member or custom role). 2. Navigate to the SSA settings page in the WordPress admin dashboard to obtain a valid REST API nonce (extractable from JS variables like `window.SSA_Data.rest_nonce`). 3. Identify the ID of an appointment assigned to a different staff member. 4. Send an authenticated GET request to `/wp-json/ssa/v1/appointments/{id}` including the REST nonce in the `X-WP-Nonce` header. 5. Observe that the response contains the full appointment record, including sensitive customer details (name, email, phone) regardless of who the appointment is assigned to.

Check if your site is affected.

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