[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fhA22BP-SrjlA3ceIc6OcA8mMggDN6djjQ2nP8dJ3NU0":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-2231","fluent-booking-unauthenticated-stored-cross-site-scripting-via-multiple-parameters","Fluent Booking \u003C= 2.0.01 - Unauthenticated Stored Cross-Site Scripting via Multiple Parameters","The Fluent Booking plugin for WordPress is vulnerable to Stored Cross-Site Scripting via multiple parameters in all versions up to, and including, 2.0.01 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.","fluent-booking",null,"\u003C=2.0.01","2.0.05","high",7.2,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:N\u002FUI:N\u002FS:C\u002FC:L\u002FI:L\u002FA:N","Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')","2026-03-25 00:00:00","2026-03-26 13:26:09",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002F37441cc0-c43c-40e4-a170-1be59e112272?source=api-prod",2,[22,23,24,25,26,27,28,29],"app\u002FHooks\u002FHandlers\u002FDataExporter.php","app\u002FHooks\u002FHandlers\u002FDataImporter.php","app\u002FHooks\u002FHandlers\u002FFrontEndHandler.php","app\u002FHttp\u002FControllers\u002FBookingController.php","app\u002FHttp\u002FControllers\u002FReportController.php","app\u002FHttp\u002FControllers\u002FSchedulesController.php","app\u002FHttp\u002FPolicies\u002FMeetingPolicy.php","app\u002FModels\u002FBooking.php","researched",false,3,"This research plan targets **CVE-2026-2231**, an unauthenticated stored Cross-Site Scripting (XSS) vulnerability in the Fluent Booking plugin.\n\n### 1. Vulnerability Summary\nThe Fluent Booking plugin (up to version 2.0.01) fails to sufficiently sanitize and escape attendee-provided data during the booking process. Specifically, when an unauthenticated user schedules a meeting via the `wp_ajax_nopriv_fluent_cal_schedule_meeting` AJAX action, several input parameters (like `name`, `message`, and custom fields) are stored in the database. While some parameters undergo basic sanitization (e.g., `sanitize_text_field`), the data is later rendered in the admin \"Schedules\" dashboard and potentially in export files (CSV\u002FJSON) without proper output escaping. This allows an attacker to inject arbitrary scripts that execute in the context of an administrative user.\n\n### 2. Attack Vector Analysis\n*   **Endpoint**: `\u002Fwp-admin\u002Fadmin-ajax.php`\n*   **Action**: `fluent_cal_schedule_meeting` (specifically the `nopriv` version)\n*   **Vulnerable Parameters**: `name`, `message`, `address`, `phone_number`, `location_description`, and `custom_fields`.\n*   **Authentication**: Unauthenticated (no account required).\n*   **Preconditions**: A Calendar and at least one active Calendar Slot (Event) must exist.\n\n### 3. Code Flow\n1.  **Entry Point**: `FrontEndHandler::register()` defines `wp_ajax_nopriv_fluent_cal_schedule_meeting` which routes to `ajaxScheduleMeeting`.\n2.  **Processing**: `BookingController::createBooking()` is invoked.\n3.  **Data Extraction**: The controller gathers input via `$request->all()`.\n4.  **Sanitization (Insufficient)**:\n    *   `name` is passed through `sanitize_text_field()`.\n    *   `message`, `phone`, and `address` are passed through `sanitize_textarea_field()`.\n    *   `custom_fields` are processed by `BookingFieldService::getCustomFieldsData()`.\n5.  **Persistence**: Data is saved to the `fcal_bookings` table via the `Booking` model.\n6.  **Sink (Rendering)**:\n    *   Admin dashboard: `SchedulesController::index()` returns booking data in JSON format. The React\u002FVue frontend renders these values (e.g., `message`) potentially using unsafe methods like `v-html` or simply failing to escape within the DOM.\n    *   Data Exports: `DataExporter::exportBookingHosts()` writes raw model values to a CSV.\n\n### 4. Nonce Acquisition Strategy\nThe booking form requires a nonce for the action `fluent_cal_schedule_meeting`. This nonce is localized into the page where the booking shortcode is present.\n\n1.  **Identify Shortcode**: The plugin uses `[fluent_booking id=\"XX\"]` or `[fluent_booking hash=\"YY\"]`.\n2.  **Setup Page**: Create a page containing a valid booking shortcode.\n3.  **Navigate**: Use the browser tool to visit this page.\n4.  **Extract Nonce**: The plugin localizes data into a global JavaScript variable named `fcal_public_vars_{calendar_id}_{event_id}`.\n5.  **JS Command**:\n    ```javascript\n    \u002F\u002F Example for Calendar 1, Event 1\n    window.fcal_public_vars_1_1?.nonce || window.fcal_public_vars_1_1?._fcal_token\n    ```\n    *Note: Based on `FrontEndHandler.php`, the localized variable name is strictly `fcal_public_vars_` + ID + `_` + EventID.*\n\n### 5. Exploitation Strategy\n1.  **Discovery**:\n    *   Find an active `event_id` and its corresponding `calendar_id`.\n    *   Find an available time slot for that event by calling the `nopriv_fluent_cal_get_available_dates` action or `nopriv_fluent_cal_get_available_slots`.\n2.  **Payload Preparation**: Use a standard XSS payload that survives `sanitize_text_field` (which strips tags but can be bypassed if the sink is an attribute or if the sanitization is skipped for specific fields).\n    *   Primary: `\u003Cimg src=x onerror=alert(document.domain)>`\n    *   Attribute Breakout: `\">\u003Csvg\u002Fonload=alert(1)>`\n3.  **Submission**:\n    *   Send a POST request to `admin-ajax.php`.\n    *   **Action**: `fluent_cal_schedule_meeting`\n    *   **Required Fields**: `name`, `email`, `timezone`, `event_time`, `_fcal_token`.\n    *   **Payload Fields**: Inject into `name`, `message`, and `address`.\n\n### 6. Test Data Setup\n1.  **Create Calendar**: Use WP-CLI to ensure at least one calendar exists.\n    ```bash\n    wp eval \"FluentBooking\\App\\Models\\Calendar::create(['title' => 'Test Calendar', 'user_id' => 1, 'type' => 'simple', 'status' => 'active']);\"\n    ```\n2.  **Create Event (Slot)**:\n    ```bash\n    wp eval \"FluentBooking\\App\\Models\\CalendarSlot::create(['calendar_id' => 1, 'title' => 'Consultation', 'slug' => 'consult', 'status' => 'active', 'duration' => 30, 'event_type' => 'one_on_one']);\"\n    ```\n3.  **Create Public Page**:\n    ```bash\n    wp post create --post_type=page --post_title=\"Book Here\" --post_content='[fluent_booking id=\"1\"]' --post_status=publish\n    ```\n\n### 7. Expected Results\n*   The `ajaxScheduleMeeting` request should return `200 OK` or `201 Created` with a success message.\n*   When an administrator navigates to **Fluent Booking > Schedules**, the injected script in the `name` or `message` field should execute immediately.\n\n### 8. Verification Steps\n1.  **Check Database**:\n    ```bash\n    wp db query \"SELECT name, message FROM wp_fcal_bookings ORDER BY id DESC LIMIT 1;\"\n    ```\n2.  **Verify Admin Execution**: Use `browser_navigate` to `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fadmin.php?page=fluent-booking#\u002Fschedules` and check for an alert or a specific DOM change.\n\n### 9. Alternative Approaches\n*   **Custom Fields**: If the main fields are sanitized, target `custom_fields`. In version 2.0.01, the custom field processing in `BookingFieldService` may not strictly sanitize all input types.\n    *   Parameter: `custom_fields[1]=\u003Cscript>alert(1)\u003C\u002Fscript>`\n*   **UTM Parameters**: The plugin captures UTM data (`utm_source`, `utm_medium`). These are often overlooked and rendered in the \"Attendee Info\" section of the schedule.\n    *   Parameter: `utm_source=\u003Cimg src=x onerror=alert(1)>`\n*   **Export Injection**: If the XSS doesn't trigger in the dashboard, check the CSV export functionality in `DataExporter::exportBookingHosts`. Inject `=cmd|' \u002FC calc'!A1` to test for CSV injection as well.","The Fluent Booking plugin for WordPress is vulnerable to unauthenticated stored Cross-Site Scripting (XSS) due to insufficient sanitization and escaping of attendee-provided data such as names, messages, and UTM parameters. Attackers can inject arbitrary scripts during the booking process that execute when an administrator views the scheduling dashboard or exports booking data.","\u002F\u002F app\u002FHooks\u002FHandlers\u002FFrontEndHandler.php:840\n        $bookingData = apply_filters('fluent_booking\u002Finitialize_booking_data', [\n            'person_time_zone' => sanitize_text_field($timezone),\n            'start_time'       => $startDateTime,\n            'name'             => sanitize_text_field($postedData['name']),\n            'email'            => sanitize_email($postedData['email']),\n            'message'          => sanitize_textarea_field(wp_unslash(Arr::get($postedData, 'message', ''))),\n            'phone'            => sanitize_textarea_field(Arr::get($postedData, 'phone_number', '')),\n            'address'          => sanitize_textarea_field(Arr::get($postedData, 'address', '')),\n            'ip_address'       => Helper::getIp(),\n            'status'           => 'scheduled',\n            'source'           => 'web',\n            'event_type'       => $calendarEvent->event_type,\n            'slot_minutes'     => $duration,\n            'utm_source'       => SanitizeService::sanitizeUtmData(Arr::get($postedData, 'utm_source', '')),\n            'utm_medium'       => SanitizeService::sanitizeUtmData(Arr::get($postedData, 'utm_medium', '')),\n            'utm_campaign'     => SanitizeService::sanitizeUtmData(Arr::get($postedData, 'utm_campaign', '')),\n            'utm_term'         => SanitizeService::sanitizeUtmData(Arr::get($postedData, 'utm_term', '')),\n            'utm_content'      => SanitizeService::sanitizeUtmData(Arr::get($postedData, 'utm_content', ''))\n        ], $postedData, $calendarEvent);\n\n---\n\n\u002F\u002F app\u002FHooks\u002FHandlers\u002FDataExporter.php:83\n        foreach ($attendees as $attendee) {\n            $row = [\n                $attendee->first_name,\n                $attendee->last_name,\n                $attendee->email,\n                $attendee->message,\n                $attendee->getLocationAsText(),\n                $attendee->source,\n                $attendee->booking_type,\n                $attendee->status,\n                $attendee->source_url,\n                $attendee->slot_minutes,\n                $attendee->start_time,\n                $attendee->end_time,\n                $attendee->payment_status,\n            ];","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ffluent-booking\u002F2.0.01\u002Fapp\u002FHooks\u002FHandlers\u002FDataExporter.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ffluent-booking\u002F2.0.05\u002Fapp\u002FHooks\u002FHandlers\u002FDataExporter.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ffluent-booking\u002F2.0.01\u002Fapp\u002FHooks\u002FHandlers\u002FDataExporter.php\t2026-01-01 11:54:56.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ffluent-booking\u002F2.0.05\u002Fapp\u002FHooks\u002FHandlers\u002FDataExporter.php\t2026-02-17 13:13:46.000000000 +0000\n@@ -80,36 +88,36 @@\n \n         foreach ($attendees as $attendee) {\n             $row = [\n-                $attendee->first_name,\n-                $attendee->last_name,\n-                $attendee->email,\n-                $attendee->message,\n-                $attendee->getLocationAsText(),\n-                $attendee->source,\n-                $attendee->booking_type,\n-                $attendee->status,\n-                $attendee->source_url,\n-                $attendee->slot_minutes,\n-                $attendee->start_time,\n-                $attendee->end_time,\n-                $attendee->payment_status,\n+                $this->sanitizeCsvCell($attendee->first_name),\n+                $this->sanitizeCsvCell($attendee->last_name),\n+                $this->sanitizeCsvCell($attendee->email),\n+                $this->sanitizeCsvCell($attendee->message),\n+                $this->sanitizeCsvCell($attendee->getLocationAsText()),\n+                $this->sanitizeCsvCell($attendee->source),\n+                $this->sanitizeCsvCell($attendee->booking_type),\n+                $this->sanitizeCsvCell($attendee->status),\n+                $this->sanitizeCsvCell($attendee->source_url),\n+                $this->sanitizeCsvCell($attendee->slot_minutes),\n+                $this->sanitizeCsvCell($attendee->start_time),\n+                $this->sanitizeCsvCell($attendee->end_time),\n+                $this->sanitizeCsvCell($attendee->payment_status),\n             ];\n \n             if ($attendee->payment_status) {\n                 $order = $attendee->payment_order;\n                 if ($order) {\n                     $order->load(['items', 'transaction']);\n-                    $row[] = $order->status;\n-                    $row[] = $order->payment_method;\n-                    $row[] = $order->currency;\n+                    $row[] = $this->sanitizeCsvCell($order->status);\n+                    $row[] = $this->sanitizeCsvCell($order->payment_method);\n+                    $row[] = $this->sanitizeCsvCell($order->currency);\n                     $row[] = $order->total_amount \u002F 100;\n-                    $row[] = $order->created_at;\n-                    $row[] = $order->transaction->id;\n-                    $row[] = $order->transaction->vendor_charge_id;\n-                    $row[] = $order->transaction->payment_method;\n-                    $row[] = $order->transaction->status;\n+                    $row[] = $this->sanitizeCsvCell($order->created_at);\n+                    $row[] = $this->sanitizeCsvCell($order->transaction->id);\n+                    $row[] = $this->sanitizeCsvCell($order->transaction->vendor_charge_id);\n+                    $row[] = $this->sanitizeCsvCell($order->transaction->payment_method);\n+                    $row[] = $this->sanitizeCsvCell($order->transaction->status);\n                     $row[] = $order->transaction->total \u002F 100;\n-                    $row[] = $order->transaction->created_at;\n+                    $row[] = $this->sanitizeCsvCell($order->transaction->created_at);\n                 }\n             } else {","To exploit this vulnerability, an unauthenticated attacker first obtains a valid public booking nonce (usually found in the localized JavaScript of any page containing the `[fluent_booking]` shortcode). The attacker then sends a POST request to the `wp_ajax_nopriv_fluent_cal_schedule_meeting` AJAX endpoint. The payload includes standard booking fields like `name`, `email`, and `event_time`, but injects XSS vectors (e.g., `\u003Cscript>alert(1)\u003C\u002Fscript>` or `\u003Cimg src=x onerror=alert(1)>`) into parameters such as `message`, `utm_source`, or custom fields. Once submitted, the malicious script is stored in the database and executes in the context of an administrator's browser session when they navigate to the Fluent Booking 'Schedules' page or export the booking data.","gemini-3-flash-preview","2026-04-17 22:56:52","2026-04-17 22:57:44",{"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.0.01","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ffluent-booking\u002Ftags\u002F2.0.01","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Ffluent-booking.2.0.01.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ffluent-booking\u002Ftags\u002F2.0.05","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Ffluent-booking.2.0.05.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ffluent-booking\u002Ftags"]