CVE-2025-68575

Wappointment <=2.7.2 - Missing Authorization

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
2.7.7
Patched in
157d
Time to patch

Description

The Wappointment plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 2.7.6. This makes it possible for unauthenticated attackers to perform an unauthorized action.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.7.6
PublishedDecember 21, 2025
Last updatedMay 26, 2026
Affected pluginwappointment

What Changed in the Fix

Changes introduced in v2.7.7

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2025-68575 ## 1. Vulnerability Summary The **Wappointment** plugin (<= 2.7.6) is vulnerable to **Missing Authorization**. The vulnerability exists because an AJAX handler (likely associated with the `Wappointment\Models\Appointment\Recurrence` logic) fails to perfo…

Show full research plan

Exploitation Research Plan: CVE-2025-68575

1. Vulnerability Summary

The Wappointment plugin (<= 2.7.6) is vulnerable to Missing Authorization. The vulnerability exists because an AJAX handler (likely associated with the Wappointment\Models\Appointment\Recurrence logic) fails to perform a capability check (e.g., current_user_can( 'manage_options' )). This allows unauthenticated attackers to trigger the generation of recurring appointment instances for any master appointment ID, potentially leading to database exhaustion, schedule manipulation, or unauthorized creation of bookable slots.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: wappointment_generate_recurrence (inferred from the Recurrence model) or wappointment_sync_recurrence.
  • Authentication: None (Unauthenticated).
  • HTTP Method: POST.
  • Payload Parameters:
    • action: wappointment_generate_recurrence (inferred)
    • appointment_id: The ID of a master recurring appointment.
    • wappointment_nonce: A valid security nonce (if enforced).

3. Code Flow

  1. An unauthenticated user sends a POST request to admin-ajax.php with a target appointment_id.
  2. The request is handled by a function registered via wp_ajax_nopriv_wappointment_generate_recurrence.
  3. The handler lacks a capability check. It fetches the appointment: $master = Appointment::find($_POST['appointment_id']).
  4. It instantiates the recurrence model: $recurrence = new \Wappointment\Models\Appointment\Recurrence($master).
  5. The constructor __construct() initializes rules and boundaries based on the staff's calendar: $this->calendar = CalendarsBack::findById($this->master->staff_id).
  6. The handler calls $recurrence->generateChilds().
  7. generateChilds() loops from the last generation date to the end of the availability window ($this->calendar['avb']).
  8. For each valid day, generateForDay() is called, which executes Appointment::create($data_new), inserting new rows into the wappo_appointments table.

4. Nonce Acquisition Strategy

The plugin likely enqueues a nonce for its booking and calendar functions.

  1. Identify Shortcode: The plugin typically uses the [wappointment] shortcode to display the booking form.
  2. Create Trigger Page:
    wp post create --post_type=page --post_title="Booking" --post_status=publish --post_content='[wappointment]'
    
  3. Navigate and Extract:
    • Use browser_navigate to visit the new page.
    • Use browser_eval to extract the nonce from the localized script object:
    • browser_eval("window.wappointment_data?.nonce") or browser_eval("window.wappo_data?.nonce").

5. Exploitation Strategy

Pre-condition

A master recurring appointment must exist. In a test environment, create one first.

Step 1: Trigger Recurrence Generation

Send an unauthenticated request to trigger the generation of future slots for a master appointment.

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=wappointment_generate_recurrence&appointment_id=1&wappointment_nonce=[EXTRACTED_NONCE]
    

Step 2: Payload Analysis

If the specific action name wappointment_generate_recurrence fails, check app/System/Init.php (if available) or search the JS assets (dist/main.js) for strings matching wp_ajax to find the exact registration.

6. Test Data Setup

  1. Staff/Calendar: Create a staff member.
  2. Service: Create a service with recurring availability enabled.
  3. Master Appointment: Create an appointment with recurrence options:
    # Example using WP-CLI to simulate a master appointment in the database
    wp db query "INSERT INTO wp_wappo_appointments (staff_id, client_id, start_at, end_at, options) VALUES (1, 1, '2025-01-01 10:00:00', '2025-01-01 11:00:00', '{\"recurrence\":{\"days\":[\"monday\",\"wednesday\"],\"last_gen\":\"2025-01-01 11:00:00\"}}')"
    
  4. Note the ID: Capture the ID of the inserted row (usually 1).

7. Expected Results

  • The server responds with a 200 OK (often returning JSON like {"success": true}).
  • The wappo_appointments table will be populated with new entries representing future occurrences of the master appointment.
  • The last_gen value in the master appointment's options column will be updated to the end of the availability window.

8. Verification Steps

  1. Check Database Count:
    wp db query "SELECT COUNT(*) FROM wp_wappo_appointments WHERE parent = 1;"
    
    An increase in the count confirms the unauthorized generation.
  2. Check Audit Trail: Verify the last_gen updated:
    wp db query "SELECT options FROM wp_wappo_appointments WHERE id = 1;"
    

9. Alternative Approaches

If generateChilds is not directly exposed, the "Missing Authorization" might exist in:

  1. Client Update: wappointment_update_client action. An attacker could change the email or options of an arbitrary client_id (from app/Models/Client.php).
    • Body: action=wappointment_update_client&id=[TARGET_ID]&email=attacker@example.com
  2. Unauthorized Booking: If the validator Wappointment\Validators\HttpRequest\Booking is used in an endpoint that skips ownership checks, an attacker might book slots on behalf of other clients by specifying a client_id.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Wappointment plugin for WordPress (<= 2.7.6) is vulnerable to unauthorized access and modification of appointments due to predictable security tokens ('edit_key'). Unauthenticated attackers can guess these keys, which are generated using simple MD5 hashes of timestamps and IDs, allowing them to modify or cancel bookings without proper authorization.

Vulnerable Code

// app/Models/Appointment/Recurrence.php line 38
public function generateEditKey($start_at)
{
    return md5($start_at);
}

---

// app/Models/Client.php line 36
public function generateEditKey($start_at)
{
    return md5($this->id . $start_at);
}

---

// app/Validators/HttpRequest/Booking.php line 138
$result = apply_filters('wappointment_validate_booking', \true, $inputs, $this->service, $this->location, static::$startKey, $this->staff);
if ($result !== \true) {
    throw new \WappointmentException("Error Processing Request", 1);
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wappointment/2.7.6/app/Models/Appointment/Recurrence.php /home/deploy/wp-safety.org/data/plugin-versions/wappointment/2.7.7/app/Models/Appointment/Recurrence.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wappointment/2.7.6/app/Models/Appointment/Recurrence.php	2025-11-24 23:27:22.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wappointment/2.7.7/app/Models/Appointment/Recurrence.php	2026-05-24 14:04:58.000000000 +0000
@@ -38,7 +38,7 @@
     }
     public function generateEditKey($start_at)
     {        
-        return md5($start_at);
+        return bin2hex(random_bytes(16));
     }
     private function generateForDay(Carbon $start_temp)
     {
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wappointment/2.7.6/app/Models/Client.php /home/deploy/wp-safety.org/data/plugin-versions/wappointment/2.7.7/app/Models/Client.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wappointment/2.7.6/app/Models/Client.php	2026-01-18 19:07:20.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wappointment/2.7.7/app/Models/Client.php	2026-05-24 14:04:58.000000000 +0000
@@ -36,7 +36,7 @@
     }
     public function generateEditKey($start_at)
     {        
-        return md5($this->id . $start_at);
+        return bin2hex(random_bytes(16));
     }
     public function getFirstName()
     {
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wappointment/2.7.6/app/Validators/HttpRequest/Booking.php /home/deploy/wp-safety.org/data/plugin-versions/wappointment/2.7.7/app/Validators/HttpRequest/Booking.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wappointment/2.7.6/app/Validators/HttpRequest/Booking.php	2026-01-18 19:07:20.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wappointment/2.7.7/app/Validators/HttpRequest/Booking.php	2026-05-24 14:04:58.000000000 +0000
@@ -135,9 +135,11 @@
         }
         $this->validateService($inputs);
         $this->validateLocation($inputs);
-        $result = apply_filters('wappointment_validate_booking', \true, $inputs, $this->service, $this->location, static::$startKey, $this->staff);
-        if ($result !== \true) {
-            throw new \WappointmentException("Error Processing Request", 1);
+        if (!isset($this->service->options['slots'])) {
+            $result = apply_filters('wappointment_validate_booking', \true, $inputs, $this->service, $this->location, static::$startKey, $this->staff);
+            if ($result !== \true) {
+                throw new \WappointmentException("Error Processing Request", 1);
+            }
         }
         $this->generateValidation($inputs);

Exploit Outline

The exploit leverages the predictability of the 'edit_key' tokens used to manage appointments without authentication. 1. An attacker identifies a target appointment's start timestamp and the associated client or staff ID, which are often available via the frontend booking calendar. 2. The attacker calculates the MD5 hash of these values (e.g., md5($timestamp) or md5($client_id . $timestamp)) to recreate the valid edit_key. 3. The attacker then submits an unauthenticated POST or GET request to the plugin's appointment management endpoints (such as cancellation or modification actions) using the guessed key. 4. Since the plugin relies on this predictable token rather than robust authorization checks, it grants the attacker full control over the targeted appointment record.

Check if your site is affected.

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