Share
## https://sploitus.com/exploit?id=0DCA0C8F-4338-5B4D-B9FB-1FA0A32556B6
# CVE-2026-16540 โ€” Simply Schedule Appointments  WP_REST_Server::READABLE,
        'callback'            => array( $this, 'purge_appointments' ),
        'permission_callback' => array( $this, 'get_items_permissions_check' ),
    ),
) );
```

**Permission check** โ€” validates ownership of ONE appointment via `id_token`:
```php
public function get_items_permissions_check( $request ) {
    // ...
    if ( true === $this->id_token_permissions_check( $request ) ) {
        return true;  // grants access if caller proves ownership of one appointment
    }
    // ...
}
```

**Handler** โ€” ignores the token's appointment and operates site-wide:
```php
public function purge_appointments( WP_REST_Request $request ) {
    $params = $request->get_params();

    // No customer_id or appointment_id scoping โ€” deletes ALL matching appointments
    if ( isset( $params['purge_past_appointments'] ) && 'true' === $params['purge_past_appointments'] ) {
        $conditions[] = $wpdb->prepare( 'end_date format( 'Y-m-d' ) );
    }

    $sql = 'SELECT * FROM ' . $this->get_table_name()
         . ' WHERE ' . implode( ' OR ', $conditions )
         . ' ORDER BY id ASC LIMIT 5000';

    $list = $wpdb->get_results( $sql, ARRAY_A );
    // Cascade-deletes across 6 tables in committed transactions
}
```

The `id_token` is an HMAC-MD5 derived from a single appointment's `id` and `date_created`. Possessing this token proves only that the caller booked one specific appointment โ€” it conveys no site-wide privilege whatsoever.

---

## Attack Chain

```
1. Attacker visits the booking page and books any free appointment
   โ†’ receives confirmation email containing: appointment_id + id_token

2. GET /wp-json/ssa/v1/appointments/purge
     ?id=
     &token=
     &purge_past_appointments=true

3. Permission check passes (valid token for attacker's own appointment)

4. Handler selects ALL past appointments site-wide โ†’ returns PII in response
   โ†’ permanently deletes them across 6 tables (on premium editions)
```

---

## Proof of Concept

> **PoC will be published on 2026-08-05** following the coordinated disclosure embargo.

See [`poc/`](./poc/) directory after the embargo date.

---

## Impact

| Scenario | Result |
|----------|--------|
| Data disclosure | Names, emails, phone numbers, appointment details of all customers |
| Mass deletion (premium) | All historical/future appointments permanently destroyed across 6 tables |
| No recovery path | Requires full database backup restore |

---

## Remediation

The fix should scope the `purge_appointments()` handler to only operate on appointments belonging to the authenticated token's owner, or restrict the endpoint to administrators only:

```php
// Option 1: Restrict to admins only
'permission_callback' => function( $request ) {
    return current_user_can( 'ssa_manage_site_settings' );
}

// Option 2: Scope deletion to token owner's appointment only
$customer_id = $this->get_customer_id_from_token( $request );
$conditions[] = $wpdb->prepare( 'customer_id = %d', $customer_id );
```

**Fixed in version 1.6.12.6.**

---

## Disclosure Timeline

| Date | Event |
|------|-------|
| 2026-07-?? | Vulnerability discovered |
| 2026-07-22 | Submitted to WPScan |
| 2026-07-22 | CVE-2026-16540 assigned |
| 2026-07-22 | Public disclosure on WPScan |
| 2026-08-05 | PoC published |

---

## References

- [WPScan Advisory](https://wpscan.com/vulnerability/c3829294-c388-4151-9e25-a3eac7b1f1c6)
- [WordPress Plugin Page](https://wordpress.org/plugins/simply-schedule-appointments/)
- [CWE-863: Incorrect Authorization](https://cwe.mitre.org/data/definitions/863.html)
- [OWASP A5: Broken Access Control](https://owasp.org/Top10/A01_2021-Broken_Access_Control/)