CVE-2026-12754

VikBooking Hotel Booking Engine & PMS <= 1.8.12 - Reflected Cross-Site Scripting via 'layoutstyle' Parameter

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

Description

The VikBooking Hotel Booking Engine & PMS plugin for WordPress is vulnerable to Reflected Cross-Site Scripting via the 'layoutstyle' parameter in all versions up to, and including, 1.8.12 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that execute if they can successfully trick a user into performing an action such as clicking on a link. Exploitation requires the targeted page to render the [vikbooking view="roomslist"] shortcode, as the vulnerable layoutstyle parameter is only processed in that view context.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
Required
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.8.12
PublishedJune 30, 2026
Last updatedJuly 1, 2026
Affected pluginvikbooking

What Changed in the Fix

Changes introduced in v1.8.13

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-12754 (VikBooking XSS) ## 1. Vulnerability Summary The **VikBooking Hotel Booking Engine & PMS** plugin (versions <= 1.8.12) contains a reflected cross-site scripting (XSS) vulnerability via the `layoutstyle` parameter. The vulnerability exists because the plu…

Show full research plan

Exploitation Research Plan: CVE-2026-12754 (VikBooking XSS)

1. Vulnerability Summary

The VikBooking Hotel Booking Engine & PMS plugin (versions <= 1.8.12) contains a reflected cross-site scripting (XSS) vulnerability via the layoutstyle parameter. The vulnerability exists because the plugin fails to sanitize or escape this parameter when it is used within the context of the roomslist view. This view is typically triggered by the [vikbooking view="roomslist"] shortcode. An unauthenticated attacker can inject arbitrary JavaScript by tricking a user into clicking a crafted URL targeting a page containing this shortcode.

2. Attack Vector Analysis

  • Endpoint: Any WordPress page/post containing the [vikbooking view="roomslist"] shortcode.
  • Vulnerable Parameter: layoutstyle (GET parameter).
  • Authentication: Unauthenticated (No login required).
  • Preconditions: A public-facing page must exist that renders the roomslist view via the plugin's shortcode.
  • Payload Type: Reflected XSS.

3. Code Flow (Inferred from Patch and Patterns)

Based on the vulnerability description and common patterns in the VikBooking plugin (which uses a Joomla-derived MVC architecture):

  1. Entry Point: A user visits a URL like example.com/booking-page/?view=roomslist&layoutstyle=PAYLOAD.
  2. Shortcode Processing: WordPress parses [vikbooking view="roomslist"]. The plugin's shortcode handler initializes the frontend controller.
  3. Request Handling: The controller (similar in logic to the provided admin/controller.php) retrieves the request variables using VikRequest::getVar('layoutstyle', ...) or directly from $_GET.
  4. View Rendering: The controller passes execution to the roomslist view class.
  5. Sink: Within the roomslist layout template, the layoutstyle value is echoed into the HTML (likely as a CSS class name for a wrapper div or within an input field) without being passed through esc_attr() or esc_html().

4. Nonce Acquisition Strategy

Reflected XSS in a GET-based view usually does not require a nonce in WordPress, as it is a read operation designed for public consumption. However, if the plugin's frontend controller implements a global check:

  1. Target Page: Navigate to the page created in the "Test Data Setup".
  2. JS Variable Check: VikBooking often localizes data. Use the following to check for nonces if the exploit fails:
    • browser_eval("window.vbo_ajax?.nonce")
    • browser_eval("window.VikBooking?.nonce")
  3. Bypass: Since the description identifies this as reflected XSS via a URL parameter, it is highly likely that no nonce is required for the rendering phase.

5. Exploitation Strategy

The exploit involves crafting a URL that breaks out of an HTML attribute or tag context.

Step 1: Discover/Create the Target Page

Identify or create a page with the specific shortcode required to trigger the vulnerable code path.

Step 2: Craft the Payload

Since layoutstyle is likely reflected in a class attribute or a similar container:

  • Simple Alert: "><script>alert(window.origin)</script>
  • Attribute Breakout: ' onmouseover='alert(1) (if reflected inside a single-quoted attribute)

Step 3: Execute via Browser

Use the browser_navigate tool to simulate a victim clicking the link.

// Example URL for the PoC Agent
const targetUrl = "http://localhost:8080/rooms/?view=roomslist&layoutstyle=\"><script>confirm(document.domain)</script>";

6. Test Data Setup

Before testing, the environment must be prepared:

  1. Install Plugin: Ensure VikBooking <= 1.8.12 is active.
  2. Create Rooms: The roomslist view may require at least one room to render the layout.
    • Use wp eval or the plugin admin to ensure a room exists in the _vikbooking_rooms table.
  3. Create the Page:
    wp post create --post_type=page --post_title="Rooms List" --post_status=publish --post_content='[vikbooking view="roomslist"]'
    
  4. Verify Page URL: Identify the permalink of the new page (e.g., /rooms-list/).

7. Expected Results

  • When the crafted URL is loaded, the browser should execute the injected <script>.
  • If using confirm(document.domain), the automation agent should detect a dialog or the presence of the script tag in the rendered DOM.
  • The HTML source of the rendered page should contain the unescaped payload:
    <div class="vbo-rooms-list-wrapper "><script>confirm(document.domain)</script>">
    

8. Verification Steps

After the browser-based exploit, verify the vulnerability using wp-cli:

  1. Check if the page renders the payload in the response body (simulated):
    # This is a conceptual check; the agent should use browser tools
    curl -s "http://localhost:8080/rooms-list/?layoutstyle=VULN_CHECK_CANARY" | grep "VULN_CHECK_CANARY"
    
  2. Confirm the version is vulnerable:
    wp plugin get vikbooking --field=version
    

9. Alternative Approaches

If the payload "><script>... is blocked by a basic WAF or browser XSS auditor:

  1. Event Handler Payload: layoutstyle=generic-style" onmouseover="alert(1) (Requires user interaction like hovering).
  2. Image Tag Sink: layoutstyle="><img src=x onerror=alert(1)> (Self-executing).
  3. Context Check: If the reflection is inside a <script> block (e.g., as a JS variable), use:
    • layoutstyle=';alert(1);//
  4. View Parameter: If the view parameter is also required in the URL to reach the sink, ensure the URL contains both view=roomslist and layoutstyle=....
Research Findings
Static analysis — not yet PoC-verified

Summary

The VikBooking Hotel Booking Engine & PMS plugin for WordPress is vulnerable to Reflected Cross-Site Scripting (XSS) via the 'layoutstyle' parameter in the 'roomslist' view. This occurs because the plugin fails to sanitize or escape user-supplied input before echoing it back into the page. An unauthenticated attacker can exploit this by tricking a user into clicking a crafted link, resulting in the execution of arbitrary JavaScript in the context of the user's browser.

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/vikbooking/1.8.12/admin/controller.php /home/deploy/wp-safety.org/data/plugin-versions/vikbooking/1.8.13/admin/controller.php
--- /home/deploy/wp-safety.org/data/plugin-versions/vikbooking/1.8.12/admin/controller.php	2026-06-16 15:26:00.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/vikbooking/1.8.13/admin/controller.php	2026-06-30 15:55:56.000000000 +0000
@@ -13957,8 +13957,15 @@
 	 */
 	public function upload_customer_document()
 	{
-		$input = JFactory::getApplication()->input;
+		$app = JFactory::getApplication();
+
+		if (!JSession::checkToken()) {
+			// missing CSRF-proof token
+			VBOHttpDocument::getInstance($app)->close(403, JText::translate('JINVALID_TOKEN'));
+		}
+
 		$dbo   = JFactory::getDbo();
+		$input = $app->input;
 
 		$customer_id = $input->getUint('customer', 0);
 
@@ -13979,15 +13986,13 @@
 				->where($dbo->qn('id') . ' = ' . $customer_id);
 
 			$dbo->setQuery($q, 0, 1);
-			$dbo->execute();
+			$customer = $dbo->loadObject();
 
-			if (!$dbo->getNumRows())
+			if (!$customer)
 			{
 				throw new Exception(sprintf('Customer [%d] not found', $customer_id), 404);
 			}
 
-			$customer = $dbo->loadObject();
-
 			// fetch documents folder path
 			$dirpath = VBO_CUSTOMERS_PATH . DIRECTORY_SEPARATOR;
 
@@ -14044,8 +14049,7 @@
 			$result->code  = $e->getCode();
 		}
 
-		echo json_encode($result);
-		exit;
+		VBOHttpDocument::getInstance($app)->json($result);
 	}
 
 	/**
@@ -14057,8 +14061,19 @@
 	 */
 	public function delete_customer_document()
 	{
-		$input = JFactory::getApplication()->input;
+		$app = JFactory::getApplication();
+
+		if (!JSession::checkToken()) {
+			// missing CSRF-proof token
+			VBOHttpDocument::getInstance($app)->close(403, JText::translate('JINVALID_TOKEN'));
+		}
+
+		if (!JFactory::getUser()->authorise('core.delete', 'com_vikbooking')) {
+			VBOHttpDocument::getInstance($app)->close(403, JText::translate('JERROR_ALERTNOAUTHOR'));
+		}
+
 		$dbo   = JFactory::getDbo();
+		$input = $app->input;
 
 		$customer_id = $input->getUint('customer', 0);
 
@@ -14075,36 +14090,45 @@
 
 		if (!$dbo->getNumRows())
 		{
-			throw new Exception(sprintf('Customer [%d] not found', $customer_id), 404);
+			VBOHttpDocument::getInstance($app)->close(404, sprintf('Customer [%d] not found', $customer_id));
 		}
 
 		$folder = $dbo->loadResult();
 
 		if (!$folder)
 		{
-			throw new Exception('The customer does not have any documents', 500);
+			VBOHttpDocument::getInstance($app)->close(500, 'The customer does not have any documents');
 		}
 
 		$file = $input->getString('file');
 
 		if (!$file)
 		{
-			throw new Exception('File to remove not specified', 400);
+			VBOHttpDocument::getInstance($app)->close(400, 'File to remove not specified');
 		}
 
 		$path = implode(DIRECTORY_SEPARATOR, array(VBO_CUSTOMERS_PATH, $folder, $file));
 
 		if (!is_file($path)) 
 		{
-			throw new Exception(sprintf('File [%s] not found', $path), 404);
+			VBOHttpDocument::getInstance($app)->close(404, sprintf('File [%s] not found', $path));
 		}
 
-		jimport('joomla.filesystem.file');
+		/**
+		 * Accept only non-traversal paths under the customer docs folder.
+		 * 
+		 * @since 	1.18.13 (J) - 1.8.13 (WP)
+		 */
+		$path = realpath($path);
+
+		if (!$path || strpos($path, VBO_CUSTOMERS_PATH) !== 0)
+		{
+			VBOHttpDocument::getInstance($app)->close(403, 'Path not allowed for file deletion.');
+		}
 
 		$removed = JFile::delete($path);
 
-		echo json_encode(array('status' => (int) $removed));
-		exit;
+		VBOHttpDocument::getInstance($app)->json(array('status' => (int) $removed));
 	}
 
 	/**

Exploit Outline

To exploit this vulnerability, an unauthenticated attacker identifies a page on the target WordPress site that renders the [vikbooking view="roomslist"] shortcode. The attacker then constructs a URL targeting this page and appends the `view=roomslist` and `layoutstyle` GET parameters. The `layoutstyle` parameter is loaded with a payload designed to break out of HTML attribute or tag contexts, such as "><script>alert(document.domain)</script>". When a victim (especially an administrator) navigates to this crafted URL, the malicious script executes within their browser session because the plugin echoes the `layoutstyle` value directly into the HTML source without proper escaping.

Check if your site is affected.

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