Appointment Booking Calendar — Simply Schedule Appointments Booking Plugin <= 1.6.10.6 - Unauthenticated Stored Cross-Site Scripting
Description
The Appointment Booking Calendar — Simply Schedule Appointments Booking Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 1.6.10.6 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=1.6.10.6What Changed in the Fix
Changes introduced in v1.6.11.0
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-39447 ## 1. Vulnerability Summary The **Simply Schedule Appointments** plugin (<= 1.6.10.6) contains an unauthenticated stored cross-site scripting (XSS) vulnerability. The flaw exists because the plugin fails to properly sanitize and escape user-controlled i…
Show full research plan
Exploitation Research Plan - CVE-2026-39447
1. Vulnerability Summary
The Simply Schedule Appointments plugin (<= 1.6.10.6) contains an unauthenticated stored cross-site scripting (XSS) vulnerability. The flaw exists because the plugin fails to properly sanitize and escape user-controlled input during the appointment booking process. Specifically, parameters passed to the booking iframe (like booking_title) are processed using a dangerous combination of functions (html_entity_decode(urldecode(esc_attr(...)))) that effectively bypasses attribute escaping, allowing an attacker to inject scripts that are stored in the database and subsequently executed in the context of an administrative user.
2. Attack Vector Analysis
- Endpoint: The booking iframe entry point, typically accessed via
admin-ajax.php?action=ssa_booking_app_iframe_inner. - Vulnerable Parameter:
booking_title(and potentiallybooking_urlorbooking_post_id) passed viaGET. - Authentication: None required (Unauthenticated).
- Preconditions: An active "Appointment Type" must exist and be accessible via a public booking page or shortcode.
3. Code Flow
- Entry Point:
booking-app-new/iframe-inner.php - Processing (Lines 123-131):
if( isset( $_GET['booking_title'] ) ) { // VULNERABLE: html_entity_decode reverses the protection of esc_attr $ssa_booking_url_settings['booking_title'] = html_entity_decode( urldecode( esc_attr( $_GET['booking_title'] ) ) ); } - Variable Localization: The
$ssa_booking_url_settingsarray is serialized into a JavaScript object (e.g.,window.ssa_booking_app_info) and rendered into the iframe's HTML. - Submission: When a user completes a booking, the Vue.js app (
booking-app-new/dist/static/js/app.js) includes thisbooking_titlein the payload sent to the server (via AJAX actionssa_submit_appointmentor similar). - Storage:
includes/class-appointment-model.phpprocesses the insert. Whilecleanup_customer_information(Line 233) sanitizes thecustomer_informationarray usingsanitize_textarea_field, metadata fields likebooking_title(stored in the appointments table) are often not subjected to the same level of filtering. - Execution: An administrator logs into the WordPress dashboard and views the "Appointments" list. The Admin App (
admin-app/dist/static/js/app.js) fetches the appointment data via the REST API and renders thebooking_titleusing a method that does not escape HTML (e.g.,v-html), triggering the XSS.
4. Nonce Acquisition Strategy
The booking iframe is loaded via a WordPress AJAX action. Unauthenticated users need the ssa_booking_app_iframe_inner action to load the app.
- Shortcode Identification: The plugin uses the
[ssa_booking]shortcode. - Setup: Create a public page with this shortcode.
- Extraction:
- Navigate to the created page.
- The plugin localizes data into the global
ssa_booking_app_infoobject. - Use
browser_evalto extract the iframe URL and any required nonces. - JS Variable:
window.ssa_booking_app_info - Nonce Key:
window.ssa_booking_app_info?.nonce(for the booking submission) orwindow.ssa_booking_app_info?.iframe_url.
5. Exploitation Strategy
- Initialize Environment: Create an appointment type and a public page with the
[ssa_booking]shortcode. - Craft Payload:
booking_title:"><script>alert(document.domain)</script>- URL-encode the payload for the initial GET request.
- Step 1: Load Booking Iframe:
Request the iframe with the malicious parameter:GET /wp-admin/admin-ajax.php?action=ssa_booking_app_iframe_inner&booking_title=%22%3E%3Cscript%3Ealert(document.domain)%3C/script%3E - Step 2: Submit Booking:
In the context of the booking app, perform a POST request to book a slot. The payload must include the maliciousbooking_title.- URL:
/wp-admin/admin-ajax.php - Body:
action=ssa_submit_appointment&appointment_type_id=1&start_date=...&booking_title="><script>alert(1)</script>&customer_information[name]=Attacker&customer_information[email]=attacker@example.com
- URL:
- Step 3: Trigger XSS:
Login as an Admin and navigate towp-admin/admin.php?page=simply-schedule-appointments. The app will load the appointments list, fetch the malicious record, and execute the script.
6. Test Data Setup
- Create Appointment Type:
wp ssa create_appointment_type --title="Consultation" --slug="consultation" - Create Booking Page:
wp post create --post_type=page --post_title="Book Now" --post_status=publish --post_content='[ssa_booking type="consultation"]'
7. Expected Results
- The booking submission is successful, and a new appointment record is created in the
wp_ssa_appointmentstable. - The
booking_titlecolumn for the new record contains the raw script tag"><script>alert(document.domain)</script>. - When the Admin views the SSA dashboard, a JavaScript alert with the site's domain appears.
8. Verification Steps
- Check Database:
Confirm the output matches the injected payload.wp db query "SELECT booking_title FROM wp_ssa_appointments ORDER BY id DESC LIMIT 1" - Check Admin UI:
Navigate to the SSA dashboard in the browser and verify the alert triggers.
9. Alternative Approaches
- Custom Field Bypass: If
booking_titleis sanitized, check if "Custom Fields" (defined incustom_customer_information) are rendered usingv-htmlin the admin view. Even ifsanitize_textarea_fieldis used on save, it allows many characters that Vue'sv-htmlmight process dangerously if the context is an attribute. booking_urlParameter: Test if$_GET['booking_url']is handled similarly tobooking_titleiniframe-inner.php.- Notification Templates: The plugin allows admins to customize email notifications. If these templates support placeholders like
{{booking_title}}and are rendered in an admin-accessible "Preview" mode, the XSS could trigger there.
Summary
The Simply Schedule Appointments plugin for WordPress is vulnerable to unauthenticated stored Cross-Site Scripting (XSS) due to insufficient sanitization of the 'booking_title' parameter in the booking iframe. Attackers can inject malicious scripts via a URL parameter which are then stored in the database during appointment submission and executed when an administrator views the appointment list.
Vulnerable Code
// booking-app-new/iframe-inner.php lines 128-131 if( isset( $_GET['booking_title'] ) ) { $ssa_booking_url_settings['booking_title'] = html_entity_decode( urldecode( esc_attr( $_GET['booking_title'] ) ) ); }
Security Fix
@@ -127,5 +127,5 @@ if( isset( $_GET['booking_title'] ) ) { - $ssa_booking_url_settings['booking_title'] = html_entity_decode( urldecode( esc_attr( $_GET['booking_title'] ) ) ); + $ssa_booking_url_settings['booking_title'] = sanitize_text_field( wp_unslash( $_GET['booking_title'] ) ); }
Exploit Outline
The exploit is a multi-step stored XSS attack that requires no authentication. 1. Initial Payload Injection: An attacker crafts a URL targeting the plugin's booking iframe endpoint (`wp-admin/admin-ajax.php?action=ssa_booking_app_iframe_inner`) containing a malicious script in the `booking_title` parameter (e.g., `?booking_title="><script>alert(1)</script>`). 2. Bypassing Escaping: The plugin processes this parameter using `html_entity_decode(urldecode(esc_attr(...)))`. The decoding functions effectively neutralize the protection provided by `esc_attr`, allowing the raw script tags to be passed into the booking application's state. 3. Persistence: The attacker (or an unwitting user) completes a booking on this page. The Vue.js frontend includes the malicious `booking_title` in the `ssa_submit_appointment` AJAX request. The server saves this title into the `wp_ssa_appointments` database table without further sanitization. 4. Execution: When an administrator logs in and accesses the 'Appointments' management page, the dashboard fetches the malicious record. Because the admin interface renders the title using an unsafe method (like Vue's `v-html`), the script executes in the context of the administrator's session.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.