VikBooking Hotel Booking Engine & PMS <= 1.8.12 - Reflected Cross-Site Scripting via 'layoutstyle' Parameter
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:NTechnical Details
What Changed in the Fix
Changes introduced in v1.8.13
Source Code
WordPress.org SVN# 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
roomslistview 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):
- Entry Point: A user visits a URL like
example.com/booking-page/?view=roomslist&layoutstyle=PAYLOAD. - Shortcode Processing: WordPress parses
[vikbooking view="roomslist"]. The plugin's shortcode handler initializes the frontend controller. - Request Handling: The controller (similar in logic to the provided
admin/controller.php) retrieves the request variables usingVikRequest::getVar('layoutstyle', ...)or directly from$_GET. - View Rendering: The controller passes execution to the
roomslistview class. - Sink: Within the
roomslistlayout template, thelayoutstylevalue is echoed into the HTML (likely as a CSS class name for a wrapper div or within an input field) without being passed throughesc_attr()oresc_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:
- Target Page: Navigate to the page created in the "Test Data Setup".
- 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")
- 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:
- Install Plugin: Ensure VikBooking <= 1.8.12 is active.
- Create Rooms: The
roomslistview may require at least one room to render the layout.- Use
wp evalor the plugin admin to ensure a room exists in the_vikbooking_roomstable.
- Use
- Create the Page:
wp post create --post_type=page --post_title="Rooms List" --post_status=publish --post_content='[vikbooking view="roomslist"]' - 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:
- 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" - 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:
- Event Handler Payload:
layoutstyle=generic-style" onmouseover="alert(1)(Requires user interaction like hovering). - Image Tag Sink:
layoutstyle="><img src=x onerror=alert(1)>(Self-executing). - Context Check: If the reflection is inside a
<script>block (e.g., as a JS variable), use:layoutstyle=';alert(1);//
- View Parameter: If the
viewparameter is also required in the URL to reach the sink, ensure the URL contains bothview=roomslistandlayoutstyle=....
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
@@ -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.