Update Elementor Pro to 4.2.2 to Fix a Critical File Upload Vulnerability
Elementor Pro versions through 4.2.1 contain a critical unauthenticated arbitrary file upload vulnerability, tracked as CVE-2026-32475. The bug has a CVSS score of 9.8 and can lead to remote code execution and a complete site takeover when a specific public form configuration exists.
The fixed release is Elementor Pro 4.2.2. If your site uses Elementor Pro, update to that version or a later release as soon as possible.
Who is affected and when exploitation is possible
The affected software is Elementor Website Builder Pro, with an estimated 6,000,000 active installations. Every version at or below 4.2.1 is vulnerable; version 4.2.2, released on August 19, 2026, contains the complete fix.
A site is exploitable only when it has a published page containing an Elementor Pro Form widget with at least one File Upload field that is not required. An attacker doesn’t need a WordPress account because the form submission endpoint accepts requests from unauthenticated visitors.
Critical configuration to review
Prioritize pages with public Elementor Pro forms that include optional File Upload fields. Updating the plugin is the required remediation, even if you remove or alter affected fields in the meantime.
What breaks in the upload validation
Form submissions reach the ajax_send_form() method of the Ajax_Handler class. The plugin packages submitted values and uploaded files into a Form_Record, then sends File Upload fields through validation() and process_field() in ElementorProModulesFormsFieldsUpload.
The defect is in the process_field upload path’s validation stage, specifically the loop in Upload::validation(). For an optional field, an entry with UPLOAD_ERR_NO_FILE causes return to run. That exits the whole method instead of moving to the next entry in the uploaded-file array.
public function validation( $field , ClassesForm_Record $record , ClassesAjax_Handler $ajax_handler ) {
static $upload_errors = false;
if ( ! $upload_errors ) {
$upload_errors = [
UPLOAD_ERR_OK => esc_html__( 'There is no error, the file uploaded with success.' , 'elementor-pro' ),
/* translators: 1: upload_max_filesize, 2: php.ini */
UPLOAD_ERR_INI_SIZE => sprintf( esc_html__( 'The uploaded file exceeds the %1$s directive in %2$s.' , 'elementor-pro' ), 'upload_max_filesize' , 'php.ini' ),
/* translators: %s: MAX_FILE_SIZE */
UPLOAD_ERR_FORM_SIZE => sprintf( esc_html__( 'The uploaded file exceeds the %s directive that was specified in the HTML form.' , 'elementor-pro' ), 'MAX_FILE_SIZE' ),
UPLOAD_ERR_PARTIAL => esc_html__( 'The uploaded file was only partially uploaded.' , 'elementor-pro' ),
UPLOAD_ERR_NO_FILE => esc_html__( 'No file was uploaded.' , 'elementor-pro' ),
UPLOAD_ERR_NO_TMP_DIR => esc_html__( 'Missing a temporary folder.' , 'elementor-pro' ),
UPLOAD_ERR_CANT_WRITE => esc_html__( 'Failed to write file to disk.' , 'elementor-pro' ),
/* translators: %s: phpinfo() */
UPLOAD_ERR_EXTENSION => sprintf( esc_html__( 'A PHP extension stopped the file upload. PHP does not provide a way to ascertain which extension caused the file upload to stop; examining the list of loaded extensions with %s may help.' , 'phpinfo()' ), 'phpinfo()' ),
];
}
$this ->fix_file_indices();
$id = $field [ 'id' ];
$files = Utils::_unstable_get_super_global_value( $_FILES , 'form_fields' );
if ( ! empty ( $field [ 'max_files' ] ) ) {
if ( count ( $files [ $id ] ) > $field [ 'max_files' ] ) {
$error_message = sprintf(
/* translators: %d: Maximum number of allowed files. */
_n( 'You can upload only %d file.' , 'You can upload up to %d files.' , intval ( $field [ 'max_files' ] ), 'elementor-pro' ),
intval ( $field [ 'max_files' ] )
);
$ajax_handler ->add_error( $id , $error_message );
return ;
}
}
foreach ( $files [ $id ] as $index => $file ) {
// not uploaded
if ( ! $field [ 'required' ] && UPLOAD_ERR_NO_FILE === $file [ 'error' ] ) {
return ;
}
// is the file required and missing?
if ( $field [ 'required' ] && UPLOAD_ERR_NO_FILE === $file [ 'error' ] ) {
$ajax_handler ->add_error( $id , $upload_errors [ $file [ 'error' ] ] );
return ;
}
// Has any error with upload the file?
if ( $file [ 'error' ] > UPLOAD_ERR_OK ) {
$ajax_handler ->add_error( $id , $upload_errors [ $file [ 'error' ] ] );
return ;
}
// valid file type?
if ( ! $this ->is_file_type_valid( $field , $file ) ) {
$ajax_handler ->add_error( $id , esc_html__( 'This file type is not allowed.' , 'elementor-pro' ) );
}
// allowed file size?
if ( ! $this ->is_file_size_valid( $field , $file ) ) {
$ajax_handler ->add_error( $id , esc_html__( 'This file exceeds the maximum allowed size.' , 'elementor-pro' ) );
}
}
}The correct control-flow statement for an empty slot would be continue. It would skip that one empty entry and allow is_file_type_valid() plus the file-size check to examine every later entry. With return, neither check runs for any remaining file in the same field.
Why the early return can become code execution
An attacker can submit the upload field as a two-item array. The first item is empty and has the UPLOAD_ERR_NO_FILE error, which stops validation. A second item can then contain a PHP payload under an attacker-selected extension, without undergoing the field’s type or size validation.
process_field() handles the same empty entry differently: it uses continue, then processes the later file. It derives the extension from the filename supplied by the client and writes the generated filename into the Elementor forms uploads directory.
public function process_field( $field , ClassesForm_Record $record , ClassesAjax_Handler $ajax_handler ) {
$id = $field [ 'id' ];
$files = Utils::_unstable_get_super_global_value( $_FILES , 'form_fields' );
foreach ( $files [ $id ] as $index => $file ) {
if ( UPLOAD_ERR_NO_FILE === $file [ 'error' ] ) {
continue ;
}
$uploads_dir = $this ->get_ensure_upload_dir();
$file_extension = pathinfo ( $file [ 'name' ], PATHINFO_EXTENSION );
$filename = uniqid() . '.' . $file_extension ;
$filename = wp_unique_filename( $uploads_dir , $filename );
$new_file = trailingslashit( $uploads_dir ) . $filename ;
if ( is_dir ( $uploads_dir ) && is_writable ( $uploads_dir ) ) {
$move_new_file = Plugin::instance()->php_api->move_uploaded_file( $file [ 'tmp_name' ], $new_file );
if ( false !== $move_new_file ) {
// Set correct file permissions.
$perms = 0644;
@ chmod ( $new_file , $perms );
$record ->add_file( $id , $index ,
[
'path' => $new_file ,
'url' => $this ->get_file_url( $filename ),
]
);
} else {
$ajax_handler ->add_error( $id , esc_html__( 'There was an error while trying to upload your file.' , 'elementor-pro' ) );
}
} else {
$ajax_handler ->add_admin_error_message( esc_html__( 'Upload directory is not writable or does not exist.' , 'elementor-pro' ) );
}
}
}As a result, a file ending in .php can be stored in /wp-content/uploads/elementor/forms/. Requesting that uploaded file can execute its PHP on the server. Like other unrestricted file upload flaws, this creates a path to full compromise through webshells and related techniques.
Disclosure and patch timeline
The report was submitted through Wordfence’s bug bounty program on July 24, 2026, with a $15,600 bounty attached. The vendor received the validated report and proof of concept on July 27.
- July 24, 2026: The unauthenticated arbitrary file upload report was submitted.
- July 27, 2026: The report and proof-of-concept exploit were validated, then disclosed to the Elementor team.
- August 2, 2026: Elementor said it had also received a report of the issue from another researcher and was preparing a patch.
- August 19, 2026: Elementor Pro 4.2.2 was released with the full fix.
The originally assigned CVE was withdrawn because another report had already received an identifier. Use CVE-2026-32475 when tracking this issue.
Update Elementor Pro now
Check the Elementor Pro version on every WordPress site you maintain, especially sites that publish forms with file uploads. Upgrade any installation running 4.2.1 or earlier to 4.2.2 or later, then review public forms to identify optional File Upload fields that may have been exposed before the update.
After the update, inspect /wp-content/uploads/elementor/forms/ and your site logs for unexpected uploads or requests. If you find suspicious PHP files or signs that they were accessed, treat the site as potentially compromised and begin your incident-response process.
Emma Richardson
UI/UX designer and frontend developer. React and the modern JavaScript ecosystem are my expertise. Passionate about user experience and accessibility.
All posts