The Exploit
An unauthenticated attacker can bypass file type restrictions by uploading a .svgz (compressed SVG) file through any Royal Elementor Addons form that accepts file uploads, provided the form does not explicitly exclude .svgz from its whitelist.
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: target.local
Content-Type: multipart/form-data; boundary=----FormBoundary7MA4YWxkTrZu0gW
------FormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="action"
rael_form_submit
------FormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="allowed_file_types"
jpg,jpeg,png,gif,pdf,doc,docx,ppt,pptx,odt,avi,ogg,m4a,mov,mp3,mp4,mpg,wav,wmv,txt,svgz
------FormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="payload.svgz"
Content-Type: application/gzip
[binary gzip content with embedded SVG/XSS payload]
------FormBoundary7MA4YWxkTrZu0gW--
The server accepts the upload because the vulnerable file_validity() function trusts the allowed_file_types POST parameter without a hardcoded whitelist. The attacker observes a 200 response with a success message and a URL to the uploaded file. When a victim visits the uploaded .svgz file in a browser, the browser decompresses and renders it as SVG, executing embedded JavaScript in the context of the site origin.
What the Patch Did
Before
private function file_validity( $file ) {
// File type validation
if ( empty( $_POST['allowed_file_types'] ) ) {
$allowed_file_types = 'jpg,jpeg,png,gif,pdf,doc,docx,ppt,pptx,odt,avi,ogg,m4a,mov,mp3,mp4,mpg,wav,wmv,txt';
} else {
$allowed_file_types = sanitize_text_field( $_POST['allowed_file_types'] );
}
$f_extension = pathinfo( $file['name'], PATHINFO_EXTENSION );
$allowed_file_types = explode( ',', $allowed_file_types );
$allowed_file_types = array_map( 'trim', $allowed_file_types );
$allowed_file_types = array_map( 'strtolower', $allowed_file_types );
$f_extension = strtolower( $f_extension );
return ( in_array( $f_extension, $allowed_file_types ) && !in_array( $f_extension, $this->get_exclusion_list() ) );
}
After
private function file_validity( $file ) {
$whitelist = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx', 'ppt', 'pptx', 'odt', 'avi', 'ogg', 'm4a', 'mov', 'mp3', 'mp4', 'mpg', 'wav', 'wmv', 'txt'];
// File type validation
if ( empty( $_POST['allowed_file_types'] ) ) {
$allowed_file_types = 'jpg,jpeg,png,gif,pdf,doc,docx,ppt,pptx,odt,avi,ogg,m4a,mov,mp3,mp4,mpg,wav,wmv,txt';
} else {
$allowed_file_types = sanitize_text_field( $_POST['allowed_file_types'] );
}
$f_extension = pathinfo( $file['name'], PATHINFO_EXTENSION );
$f_extension = strtolower( $f_extension );
$allowed_file_types = explode( ',', $allowed_file_types );
$allowed_file_types = array_map( 'trim', $allowed_file_types );
$allowed_file_types = array_map( 'strtolower', $allowed_file_types );
return ( in_array( $f_extension, $allowed_file_types ) && in_array( $f_extension, $whitelist ) && !in_array( $f_extension, $this->get_exclusion_list() ) );
}
The patch introduces a hardcoded $whitelist array containing only the safe file types the developers intended to support. The critical addition is the second in_array() check on line 118 of the fixed version: in_array( $f_extension, $whitelist ). This enforces server-side validation that the uploaded file extension must exist in both the user-supplied $allowed_file_types array and the hardcoded whitelist. sanitize_text_field() alone is insufficient; it strips HTML tags but does not validate against a known-good set of extensions.
Root Cause
CWE-434: Unrestricted Upload of File with Dangerous Type
The vulnerability exists because file extension validation relies entirely on a user-controlled POST parameter. The dataflow is: attacker controls $_POST['allowed_file_types'] → passed through sanitize_text_field() (which does not restrict characters to a whitelist) → parsed into $allowed_file_types array → compared against extracted $f_extension via in_array(). The trust boundary is crossed at the initial $_POST read. The code assumes the form builder (the admin who configured the form) is trustworthy, but does not verify that the client performing the upload respects that configuration. An unauthenticated attacker can craft their own POST request with a malicious allowed_file_types value, and the server will accept it.
Why It Works
The load-bearing line is in_array( $f_extension, $whitelist ) — without it, an attacker can still inject arbitrary extensions into the POST parameter and bypass the first in_array() check. Removing this line resurrects the bug entirely. The engineer also defined $whitelist as a hardcoded array constant rather than reading it from a configuration or database, ensuring it cannot be modified by request parameters. The reordering of extension normalization (moving strtolower() earlier) ensures the comparison is consistent, but the whitelist check is the actual security boundary. The exclusion list check (!in_array( $f_extension, $this->get_exclusion_list() )) provides defence in depth — it allows for runtime blocking of specific extensions without code changes — but it is too weak to stand alone because get_exclusion_list() may be empty or incomplete in older installations.
Hardening Checklist
- Define a hardcoded allowlist of permitted file extensions as a class property or constant; never derive the allowlist from user input, query parameters, or database configuration. Use
in_array( $extension, $this->allowed_extensions, true )with strict type checking. - Validate file MIME type in addition to extension using
wp_check_filetype_and_ext()orfinfo_file(), not justpathinfo(), to prevent double-extension attacks (e.g.,shell.php.jpg). - Implement
wp_handle_upload()with awp_handle_upload_prefilterhook that enforces whitelist validation before the file is moved to the uploads directory, rather than validating after the user submits the form. - Use
wp_safe_remote_get()to download a reference MIME database or use WordPress's built-in MIME type registry; do not trust$_FILES['file']['type'], which is entirely client-controlled. - Store uploaded files outside the web root or in a directory with
.htaccessrules that disable script execution:php_flag engine offandAddType text/plain .php .phtml .php3 .php4 .php5 .phps.
References
- https://nvd.nist.gov/vuln/detail/CVE-2024-1567
- https://www.wordfence.com/threat-intel/vulnerabilities/id/Royal-Elementor-Addons