The Exploit
An unauthenticated attacker can read or delete arbitrary files from the server by supplying a path-traversal sequence in the source parameter to wfu_file_downloader.php.
GET /wp-content/plugins/wp-file-upload/lib/wfu_file_downloader.php?source=../../../../../../../etc/passwd HTTP/1.1
Host: target.local
Connection: close
The server responds with the contents of /etc/passwd or, if the file does not exist in the temp directory under the guessed name, an error that leaks the attempted path. An attacker observes either file contents in the response body or—if the downloader script also deletes files post-transmission—silent file deletion on disk. Because no authentication is enforced, the attacker requires zero credentials.
What the Patch Did
Before
$source = (isset($_POST['source']) ? $_POST['source'] : (isset($_GET['source']) ? $_GET['source'] : ''));
if ( $source === '' ) die();
$filepath = sys_get_temp_dir();
if ( substr($filepath, -1) != '/' ) $filepath .= '/';
$filepath .= $source;
After
$source = (isset($_POST['source']) ? $_POST['source'] : (isset($_GET['source']) ? $_GET['source'] : ''));
if ( $source === '' ) die();
// sanitize source file to avoid directory traversals through it
$source = preg_replace("/[^A-Za-z0-9]/", "", $source);
$filepath = sys_get_temp_dir();
if ( substr($filepath, -1) != '/' ) $filepath .= '/';
$filepath .= $source;
The patch applied whitelist-based input sanitization using preg_replace("/[^A-Za-z0-9]/", "", $source). This regular expression strips all non-alphanumeric characters from the user-supplied filename, eliminating path separators (/, \), dots (.), and other special characters used in directory-traversal attacks. The control is a regex-based character class filter — a common but blunt mitigation for CWE-22.
Root Cause
CWE-22: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').
The source parameter arrives unauthenticated from either $_GET['source'] or $_POST['source']. The script reads this value directly and concatenates it to the system temporary directory path without validation or canonicalization. The attacker-controlled string crosses a trust boundary by being used to construct a filesystem path that the script then opens, reads, or deletes. Because no check constrains the filename to the temporary directory, sequences like ../../../etc/passwd escape the temp directory and access arbitrary locations on the filesystem, violating the assumed boundary of sys_get_temp_dir().
Why It Works
The single load-bearing line is:
$source = preg_replace("/[^A-Za-z0-9]/", "", $source);
If this line were removed, the bug remains exploitable: ../../../etc/passwd would pass through unchanged and be concatenated onto sys_get_temp_dir(), producing a path to an arbitrary file. The regex is necessary because it destroys the syntax of path-traversal payloads. The dot in .. and the slash in / are stripped, leaving only alphanumeric fragments that—when appended to the temp directory—refer only to filenames within that directory.
The patch does not use realpath() to canonicalize the path after concatenation, nor does it use basename() to extract only the filename component, nor does it check the resolved path against a whitelist. Instead, it prevents the attacker from ever constructing a traversal sequence in the first place. This is defense-in-depth by elimination: if the attacker cannot write a dot or slash, they cannot write a traversal. The strategy is sound but strict—it also rejects legitimate filenames that contain hyphens, underscores, or other punctuation, which may break legitimate use cases if the original design relied on such characters. However, for a download-handler script that generates temporary filenames, alphanumeric-only is a reasonable constraint.
Hardening Checklist
- Use
basename()on any user-supplied filename before constructing a filesystem path.$filename = basename($_GET['source']);would reject any path component and enforce that only a bare filename—no directory traversal—is accepted. - Implement a whitelist of allowed filenames by storing the actual temp file names in a database or session, and permitting downloads only of files present in that list. This makes the downloader independent of user input for path construction.
- Apply
wp_verify_nonce()or similar CSRF/authentication checks to the download endpoint, even if it serves public files. This prevents CSRF and reduces the blast radius of other bugs in the same script. - Use
wp_safe_remote_get()or similar wrapper functions instead of raw filesystem operations to retrieve file content; these functions apply additional safety checks and logging. - Canonicalize the final path with
realpath()after concatenation, then verify that the result still lies within the intended directory usingstrpos()or similar. This catches both../and symlink-based traversals:if (strpos(realpath($filepath), realpath(sys_get_temp_dir())) !== 0) die('Access denied');.
References
- https://nvd.nist.gov/vuln/detail/CVE-2024-11613