GHSA-w8xc-8g92-v77h Easy!Appointments appointments/store and appointments/update allow cross-provider appointment injection — Authorization Bypass
SummaryEasy!Appointments correctly filters provider-scoped appointments in the `appointments/search` response, proving that provider isolation is an intended security boundary. However, the direct mutation endpoints `appointments/store` and `appointments/update` only check generic appointment privileges and never verify that the submitted `id_users_provider` belongs to the current session.A normal authenticated provider can inject new appointments into another provider's schedule via `store`, or reassign existing appointments into a foreign provider's calendar via `update`. The `store` path contains an additional write-before-crash bug: the unauthorized row is committed to the database before the controller crashes on a type error, so the attacker receives an error response while the foreign appointment is already persisted.Root Cause — Step by Step Code FlowStep 1 — Search correctly enforces provider isolationThe `search` endpoint filters out appointments belonging to other providers — proving provider isolation is an intended boundary:Step 2 — store() only checks generic add permissionThe store endpoint checks only whether the caller can add appointments in general — no provider ownership check:Step 3 — Attacker-controlled id_users_provider saved directlyThe controller whitelists and persists the appointment including the attacker-controlled provider ID:No check is performed to verify `id_users_provider` matches the current session.Step 4 — Write-before-crash on store()After the unauthorized row is committed, the controller crashes on a type error:The attacker receives a 500 error response, but the foreign appointment row is already in the database.Step 5 — update() has the same missing ownership checkThe update endpoint also accepts attacker-controlled `id_users_provider` with only a generic edit permission check:A provider can reassign any appointment they can edit into a foreign provider's calendar.Proof of Concept**Attacker:** Provider with session ID `4`
**Target:** Foreign provider with ID `2`**Step 1 — Inject appointment into foreign provider's schedule:**Response: `500 Internal Server Error` — type error on find()**But the row is committed:**Provider 4 created a row that belongs to provider 2.**Step 2 — Reassign existing appointment to foreign provider:**Response: `200 OK`**Result:** Appointment 7 now belongs to provider 2 instead of provider 4.**Runtime verification result:**The attacker's appointment search returns 0 results because both proof appointments now belong to the foreign provider.Real World ImpactIn a multi-provider Easy!Appointments deployment — a clinic, salon, or service business with multiple staff members — any authenticated provider can:Inject fake appointments into a colleague's schedule causing confusion and double-bookingsMove their own appointments into another provider's calendar to hide or offload themDisrupt scheduling integrity for staff and customersCreate unauthorized appointments that customers and admins attribute to the wrong provider
The write-before-crash bug in `store()` makes detection harder — the attacker receives an error response that may appear harmless while the unauthorized record is silently committed.Suggested Fix**In `store()` and `update()`:** Enforce that `id_users_provider` matches the current session provider ID for non-admin roles:**Fix the write-before-crash bug in `store()`:**Reporter**Yash Shendge (ashrexon)**
2026-05-25
CVSS v3.1 score: 3.3