CVE-2026-39541

Hydra Booking <= 1.1.38 - Authenticated (Hydra host+) Stored Cross-Site Scripting

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
1.1.39
Patched in
60d
Time to patch

Description

The Hydra Booking plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 1.1.38 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with hydra host-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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.1.38
PublishedFebruary 15, 2026
Last updatedApril 15, 2026
Affected pluginhydra-booking

What Changed in the Fix

Changes introduced in v1.1.39

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-39541 ## 1. Vulnerability Summary The **Hydra Booking** plugin for WordPress is vulnerable to **Stored Cross-Site Scripting (XSS)** in versions up to and including 1.1.38. The vulnerability exists because the plugin fails to sanitize and escape settings value…

Show full research plan

Exploitation Research Plan - CVE-2026-39541

1. Vulnerability Summary

The Hydra Booking plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) in versions up to and including 1.1.38. The vulnerability exists because the plugin fails to sanitize and escape settings values (specifically appearance and theme colors) before storing them and subsequently rendering them within an inline <style> block. An authenticated attacker with tfhb_host (Hydra host) privileges or higher can inject malicious JavaScript that executes in the context of any user visiting the site (frontend or admin).

2. Attack Vector Analysis

  • Endpoint: /wp-json/hydra-booking/v1/settings/appearance-settings/update
  • HTTP Method: POST
  • Payload Parameter: primary_color (within a JSON body)
  • Authentication: Authenticated, user role tfhb_host or higher.
  • Permissions: The endpoint is protected by a permission callback (likely tfhb_manage_options_permission or similar), which the tfhb_host role is granted or can bypass due to logic flaws in the plugin's capability mapping.
  • Preconditions: A user with the tfhb_host role must be created.

3. Code Flow

  1. Injection Sink: In app/Enqueue.php, the function tfhb_enqueue_scripts() retrieves settings from the option _tfhb_appearance_settings.
    • It extracts values like $tfhb_primary_color = $_tfhb_appearance_settings['primary_color'].
    • It constructs a CSS string: $tfhb_theme_css = ":root { --tfhb-primary-color: $tfhb_primary_color; ... }".
    • It outputs this via wp_add_inline_style( 'tfhb-style', $tfhb_theme_css ).
    • Crucially, the variables are concatenated directly into the CSS string without any sanitization or escaping.
  2. Data Storage: In admin/Controller/SettingsController.php, the REST route /settings/appearance-settings/update calls a handler (e.g., UpdateAppearanceSettings) which takes the POST body and saves it to the _tfhb_appearance_settings option using update_option().

4. Nonce Acquisition Strategy

The REST API requires a valid wp_rest nonce. This nonce is localized by the plugin for the React-based admin interface.

  1. Identify Page: The script tfhb-admin-core is enqueued on the Hydra Booking admin page (admin.php?page=hydra-booking).
  2. Navigation: Navigate to /wp-admin/admin.php?page=hydra-booking as the tfhb_host user.
  3. Extraction: Use browser_eval to extract the nonce from the localized JavaScript object tfhb_core_apps.
    • JavaScript Variable: window.tfhb_core_apps
    • Key: rest_nonce
    • Command: browser_eval("window.tfhb_core_apps?.rest_nonce")

5. Exploitation Strategy

  1. Setup Host User: Ensure a user exists with the role tfhb_host.
  2. Extract Nonce:
    • Log in as the tfhb_host user.
    • Navigate to http://localhost:8080/wp-admin/admin.php?page=hydra-booking.
    • Run browser_eval("window.tfhb_core_apps.rest_nonce") to get the $REST_NONCE.
  3. Submit Payload:
    • Use http_request to send a POST request to the REST endpoint.
    • URL: http://localhost:8080/wp-json/hydra-booking/v1/settings/appearance-settings/update
    • Headers:
      • Content-Type: application/json
      • X-WP-Nonce: $REST_NONCE
    • Body:
      {
        "primary_color": "red; } </style><script>alert(document.domain)</script><style> .dummy { color: "
      }
      
  4. Trigger Execution: Visit the site homepage or any admin page where the plugin enqueues styles. The injected script will execute immediately.

6. Test Data Setup

  • User: Create a user host_attacker with the password password123 and role tfhb_host.
  • Plugin State: Ensure the Hydra Booking plugin is active.
  • Shortcode: Create a page with the shortcode [hydra_booking] to ensure frontend scripts/styles are loaded if necessary.

7. Expected Results

  • The REST API should return a success message (e.g., {"status": true, ...}).
  • The WordPress option _tfhb_appearance_settings will now contain the XSS payload.
  • When visiting the homepage, the HTML source will contain:
    <style id='tfhb-style-inline-css' type='text/css'>
    :root {
        --tfhb-primary-color: red; } </style><script>alert(document.domain)</script><style> .dummy { color: ;
        ...
    }
    </style>
    
  • A browser alert box showing the document domain will appear.

8. Verification Steps

  1. Check Option Value: Use WP-CLI to verify the stored value:
    • wp option get _tfhb_appearance_settings
  2. Verify Payload in Source: Use http_request (GET) to the homepage and grep for the script tag:
    • Look for <script>alert(document.domain)</script>.

9. Alternative Approaches

If the appearance-settings update fails, target the Frontend Dashboard settings which are also vulnerable to CSS breakout in admin/Controller/Enqueue.php.

  • Endpoint: /wp-json/hydra-booking/v1/settings/general/update (Verify if this updates the _tfhb_frontend_dashboard_settings option).
  • Target Page: Any page using the tfhb-frontend-dashboard.php template.
  • Payload: Same CSS breakout technique applied to the primery_default parameter.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Hydra Booking plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) due to insufficient sanitization and output escaping of theme and appearance settings. Authenticated attackers with 'tfhb_host' privileges or higher can inject arbitrary JavaScript into settings that are subsequently rendered within inline CSS blocks on the frontend and admin dashboard.

Vulnerable Code

// File: app/Enqueue.php
// Lines 32-58

		$tfhb_primary_color   = ! empty( $_tfhb_appearance_settings['primary_color'] ) ? $_tfhb_appearance_settings['primary_color'] : '#2E6B38';
		$tfhb_primary_hover   = ! empty( $_tfhb_appearance_settings['primary_hover'] ) ? $_tfhb_appearance_settings['primary_hover'] : '#4C9959';
		$tfhb_secondary_color = ! empty( $_tfhb_appearance_settings['secondary_color'] ) ? $_tfhb_appearance_settings['secondary_color'] : '#273F2B';
		$tfhb_secondary_hover = ! empty( $_tfhb_appearance_settings['secondary_hover'] ) ? $_tfhb_appearance_settings['secondary_hover'] : '#E1F2E4';
		$tfhb_text_title_color = ! empty( $_tfhb_appearance_settings['text_title_color'] ) ? $_tfhb_appearance_settings['text_title_color'] : '#141915';
		$tfhb_paragraph_color = ! empty( $_tfhb_appearance_settings['paragraph_color'] ) ? $_tfhb_appearance_settings['paragraph_color'] : '#273F2B';
		$tfhb_surface_primary = ! empty( $_tfhb_appearance_settings['surface_primary'] ) ? $_tfhb_appearance_settings['surface_primary'] : '#C0D8C4';
		$tfhb_surface_background = ! empty( $_tfhb_appearance_settings['surface_background'] ) ? $_tfhb_appearance_settings['surface_background'] : '#EEF6F0';
		$tfhb_theme_css       = "
        :root {
            --tfhb-primary-color: $tfhb_primary_color;
            --tfhb-primary-hover-color: $tfhb_primary_hover;
            --tfhb-secondary-color: $tfhb_secondary_color;
            --tfhb-secondary-hover-color: $tfhb_secondary_hover;
            --tfhb-paragraph-color: $tfhb_paragraph_color;
            --tfhb-text-title-color: $tfhb_text_title_color;
            --tfhb-surface-primary-color: $tfhb_surface_primary;
            --tfhb-surface-background-color: $tfhb_surface_background;
          }
        ";
		wp_add_inline_style( 'tfhb-style', $tfhb_theme_css ); 

---

// File: admin/Controller/Enqueue.php
// Lines 121-147

		if($front_end_dashboard == true){
			$settings = !empty(get_option('_tfhb_frontend_dashboard_settings')) ? get_option('_tfhb_frontend_dashboard_settings') : array();
			$primery_default  = isset($settings['general']['primery_default']) ? $settings['general']['primery_default'] : '#2E6B38'; 
			$primery_hover  = isset($settings['general']['primery_hover']) ? $settings['general']['primery_hover'] : '#4C9959'; 
            // ... (truncated)
			$custom_css = "
				:root {
					--tfhb-admin-primary-default: $primery_default; 
					--tfhb-admin-primary-hover: $primery_hover; 
                    // ... (truncated)
				} 
			";
			wp_add_inline_style('tfhb-admin-style', $custom_css);
		}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/hydra-booking/1.1.38/admin/Controller/Enqueue.php /home/deploy/wp-safety.org/data/plugin-versions/hydra-booking/1.1.39/admin/Controller/Enqueue.php
--- /home/deploy/wp-safety.org/data/plugin-versions/hydra-booking/1.1.38/admin/Controller/Enqueue.php	2026-02-15 09:54:22.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/hydra-booking/1.1.39/admin/Controller/Enqueue.php	2026-02-27 17:36:30.000000000 +0000
@@ -121,17 +121,18 @@
 
 		if($front_end_dashboard == true){
 			$settings = !empty(get_option('_tfhb_frontend_dashboard_settings')) ? get_option('_tfhb_frontend_dashboard_settings') : array();
-			$primery_default  = isset($settings['general']['primery_default']) ? $settings['general']['primery_default'] : '#2E6B38'; 
-			$primery_hover  = isset($settings['general']['primery_hover']) ? $settings['general']['primery_hover'] : '#4C9959'; 
-			$secondary_default  = isset($settings['general']['secondary_default']) ? $settings['general']['secondary_default'] : '#273F2B'; 
-			$secondary_hover  = isset($settings['general']['secondary_hover']) ? $settings['general']['secondary_hover'] : '#E1F2E4'; 
-			$text_title  = isset($settings['general']['text_title']) ? $settings['general']['text_title'] : '#141915'; 
-			$text_paragraph  = isset($settings['general']['text_paragraph']) ? $settings['general']['text_paragraph'] : '#273F2B';  
-			$surface_primary  = isset($settings['general']['surface_primary']) ? $settings['general']['surface_primary'] : '#F9FBF9';  
-			$surface_background  = isset($settings['general']['surface_background']) ? $settings['general']['surface_background'] : '#C0D8C4';  
-			$surface_border  = isset($settings['general']['surface_border']) ? $settings['general']['surface_border'] : '#C0D8C4';  
-			$surface_border_hover  = isset($settings['general']['surface_border_hover']) ? $settings['general']['surface_border_hover'] : '#211319';  
-			$surface_input_field  = isset($settings['general']['surface_input_field']) ? $settings['general']['surface_input_field'] : '#56765B';  
+			// Validate color values - only allow valid hex colors (#RGB or #RRGGBB format)
+			$primery_default  = $this->validate_hex_color( $settings['general']['primery_default'] ?? '#2E6B38', '#2E6B38' );
+			$primery_hover  = $this->validate_hex_color( $settings['general']['primery_hover'] ?? '#4C9959', '#4C9959' );
+			$secondary_default  = $this->validate_hex_color( $settings['general']['secondary_default'] ?? '#273F2B', '#273F2B' );
+			$secondary_hover  = $this->validate_hex_color( $settings['general']['secondary_hover'] ?? '#E1F2E4', '#E1F2E4' );
+			$text_title  = $this->validate_hex_color( $settings['general']['text_title'] ?? '#141915', '#141915' );
+			$text_paragraph  = $this->validate_hex_color( $settings['general']['text_paragraph'] ?? '#273F2B', '#273F2B' );
+			$surface_primary  = $this->validate_hex_color( $settings['general']['surface_primary'] ?? '#F9FBF9', '#F9FBF9' );
+			$surface_background  = $this->validate_hex_color( $settings['general']['surface_background'] ?? '#C0D8C4', '#C0D8C4' );
+			$surface_border  = $this->validate_hex_color( $settings['general']['surface_border'] ?? '#C0D8C4', '#C0D8C4' );
+			$surface_border_hover  = $this->validate_hex_color( $settings['general']['surface_border_hover'] ?? '#211319', '#211319' );
+			$surface_input_field  = $this->validate_hex_color( $settings['general']['surface_input_field'] ?? '#56765B', '#56765B' );
 			$custom_css = "
 				:root {
 					--tfhb-admin-primary-default: $primery_default; 
@@ -154,4 +155,29 @@
 			wp_enqueue_media();
 		}
 	}
+
+	/**
+	 * Validate and sanitize hex color values.
+	 */
+	private function validate_hex_color( $color, $default_color = '#000000' ) {
+		if ( empty( $color ) ) {
+			return $default_color;
+		}
+		$color = trim( $color );
+		if ( preg_match( '/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/', $color ) ) {
+			return $color;
+		}
+		return $default_color;
+	}

Exploit Outline

1. Authenticate to the WordPress site with a user having the 'tfhb_host' role. 2. Navigate to the Hydra Booking admin page to extract the REST API nonce from the `window.tfhb_core_apps.rest_nonce` global variable. 3. Send a POST request to the `/wp-json/hydra-booking/v1/settings/appearance-settings/update` REST endpoint. 4. In the JSON request body, set a color parameter (e.g., `primary_color`) to a payload that closes the CSS block and style tag, then executes JavaScript: `red; } </style><script>alert(document.domain)</script><style> .dummy { color: `. 5. The payload will be saved to the database without sanitization. 6. Visit any page on the site where the plugin enqueues styles (e.g., the homepage or a page with the booking shortcode) to trigger the script execution.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.