Book appointment Online < 1.39 - Authenticated Stored Cross-Site Scripting (XSS)
Introduction
I was reviewing appointment-booking plugins to check how they render merchant-defined data back into admin/listing views. Fields like “service name,” “duration,” and “price” often look harmless, yet they’re frequently echoed across multiple templates. During this pass, I focused on the Book appointment Online plugin and traced how it prints the Service Prices field—both in the admin list and the user-facing list. My aim was simple: confirm whether user-supplied values were properly sanitized/escaped before being sent to the browser. The plugin versions prior to 1.39 did not handle this safely, which opened the door to a stored cross-site scripting (XSS) issue under authenticated contexts.
Summary of the Finding
The plugin did not sanitize or escape the “Service Prices” field before outputting it in the list view. As a result, a high-privilege authenticated user (e.g., Editor/Shop-manager-like role inside the plugin) could store HTML/JS payloads in the price value that would execute whenever the list was rendered—even when the WordPress unfiltered_html capability was disallowed. Practically, this enables session-riding within the same origin (stealing nonces, performing privileged actions, or altering plugin settings) depending on which page reflects the payload. The issue is cataloged as CVE-2021-24614, fixed in v1.39.
Reproduction steps (tested on local instance)
Performed only on a local test instance for verification and learning.
- Installed a vulnerable release of the plugin in a local WordPress instance. Created two accounts: an administrator and a high-privilege non-admin user who can manage services within the plugin.
- Navigated to the plugin’s Services section and confirmed that the Service Prices column is rendered in the admin list and (depending on configuration) in a front-end list.
- To test for reflected/echoed content, I started with an innocuous HTML probe in the Service Prices field (e.g., 123). Seeing the tags echoed back unescaped, I escalated to a standard stored-XSS probe that survives attribute filtering in many contexts:
1
<img src=x onerror=alert(1)>
- As an alternative (and sometimes more reliable in SVG-friendly contexts):
1
<svg onload=alert(1)>
- Saved the service. Reloaded the Services list and browsed the corresponding page where the price is printed. The payload executed when the list rendered, and the XSS will be triggered in the Services list
/wp-admin/edit.php?post_type=services
, confirming a stored XSS vector tied to the price field.
Timelines
- Publicly Published : 2021-08-10
- Added : 2021-08-10
- Last Updated : 2021-08-10