WordPress HT Contact Form 7 Plugin <= 2.2.1 is vulnerable to a high priority Arbitrary File Upload

Overview
Published: 2025-07-15 CVE-ID: CVE-2025-7340 CVSS: 10 Critical Affected Plugin: WordPress HT Contact Form 7 Plugin Affected Versions: <= 2.2.1 Vulnerability Type: High priority Arbitrary File Upload CWE: CWE-434 Unrestricted Upload of File with Dangerous Type
Description
The HT Contact Form Widget For Elementor Page Builder & Gutenberg Blocks & Form Builder. plugin for WordPress is vulnerable to arbitrary file uploads due to missing file type validation in the temp_file_upload function in all versions up to, and including, 2.2.1. This makes it possible for unauthenticated attackers to upload arbitrary files on the affected site’s server which may make remote code execution possible.
Patch And Commit Analysis

Looking at the plugin’s change log, we see a stark contrast between the two versions, revealing a massive security oversight by the developer in the previous release:
- Version 2.2.1 (Vulnerable): This version basically “left the door wide open” for hackers. At this stage, the temporary file upload handler (temp_file_upload) had zero barriers. The plugin accepted any file type sent by users without asking a single question—whether it was a harmless image or malicious code. This fundamental flaw turned a convenient feature into a red carpet for Remote Code Execution (RCE) attacks.
- The “Emergency” Fix in Version 2.2.2: Just one day later, version 2.2.2 was rushed out with a telling note: “Improved: File upload handling by adding file type validation.” The urgent addition of file type validation and filename sanitization is the clearest evidence that, in version 2.2.1, these basic security mechanisms were completely ignored.
Vulnerable Code and Patch Code Analysis
Arbitrary File Upload
The code uses the temp_file_upload function in the Ajax class to handle temporary file uploads via Ajax. After checking if a file is present, this function invokes the temp_file_upload method in the FileManager class:
public function temp_file_upload() {
check_ajax_referer('ht_form_ajax_nonce', '_wpnonce');
$file = $_FILES['ht_form_file'];
// Check if file is present
if (!isset($file)) {
wp_send_json_error('No file uploaded.');
}
return $this->file_manager->temp_file_upload($file);
}
public function temp_file_upload($file) {
$destination = "{$this->dir}/temp";
$this->maybe_create_directories($destination);
// Validate file
$validation = $this->validate($file);
if (!$validation['valid']) {
wp_send_json_error($validation['message']);
return;
}
// Process the file
$filename = $this->process_filename($file['name']);
$file_path = "{$destination}/$filename";
// File type validation check
$validate = wp_check_filetype( $filename );
if ($validate['type'] === false) {
wp_send_json_error('Invalid file type.');
return;
}
// Check if directory is writable
if (!is_writable($destination)) {
wp_send_json_error("Directory is not writable: {$destination}");
return;
}
// Move file to temporary directory
if (move_uploaded_file($file['tmp_name'], $file_path)) {
wp_send_json_success([
'file_id' => $filename,
'file_name' => $file['name'],
'file_size' => $file['size']
]);
return;
} else {
$upload_error = error_get_last();
wp_send_json_error('Failed to save uploaded file. Error: ' . ($upload_error ? $upload_error['message'] : 'Unknown error'));
return;
}
}
Vulnerability Analysis As shown above, the function lacks proper validation mechanisms such as:
- File type restrictions (only allowing safe formats like .jpg, .png, .pdf)
- MIME type verification
- Extension checks
Because of this, attackers can upload arbitrary files — including .php scripts containing malicious code. If these files are executed on the server, they can lead to Remote Code Execution (RCE), giving attackers full control over the site.
Patch Analysis

Looking at the patch of FileManager.php, we can clearly see that Developers add an File type validation check for temp_file_upload at the patch. So at the patch version, temporary files file type will be checked before moving into temporary directory.
Arbitrary File Deletion
During code analysis, the plugin using temp_file_delete function to delete temporary file via Ajax.
/**
* Handle Delete Temporary File on AJAX
*/
public function temp_file_delete() {
check_ajax_referer('ht_form_ajax_nonce', '_wpnonce');
$file_id = isset($_POST['ht_form_file_id']) ? sanitize_file_name(wp_unslash($_POST['ht_form_file_id'])) : '';
if (!$file_id) {
wp_send_json_error('No file ID provided');
}
if ($this->file_manager->temp_file_delete($file_id)) {
wp_send_json_success();
} else {
wp_send_json_error('Failed to delete file');
}
}
This temp_file_delete function also invokes the temp_file_delete at FileManager class and it using to delete temporary file from temp folder.
/**
* Delete temporary file
* @return bool Return `true` if file deleted successfully else `false`
*/
public function temp_file_delete($file_id) {
$file = "{$this->dir}/temp/$file_id";
if (file_exists($file) && is_writable($file)) {
@unlink($file);
return true;
}
return false;
}
Vulnerability Analysis
Because the file_id parameter isn’t properly validated, attackers can supply arbitrary paths. This flaw allows unauthenticated users to delete any file on the server, including critical ones like wp-config.php. Removing that file forces WordPress into its installation setup, which attackers can then hijack by pointing it to a database they control. From there, they gain access to the site’s server and can escalate the compromise with further malicious actions.
Patch Analysis

Developers also apply an patch at Ajax.php, in details, they have change the function inside temp_file_delete() from sanitize_text_field to sanitize_file_name, both of them also are wordpress core funcion.
Arbitrary File Move
In further analysis, we also can see that plugin using handle_file_upload function to control upload mechanism in submission.php.
public function handle_files_upload($form_data, $form) {
foreach ($form['fields'] as $field) {
if ($field['type'] === 'file_upload') {
$destination = $field['settings']['upload_location'] ?? 'ht_form_default';
$files = $form_data[$field['settings']['name_attribute']];
if (!empty($files)) {
foreach ($files as $key => $file) {
$file = sanitize_file_name($file);
$form_data[$field['settings']['name_attribute']][$key] = $this->upload_file($file, $destination);
}
}
}
}
return $form_data;
}
This handle_files_upload function also invokes the upload_file at submission class.
public function upload_file($file_name, $destination) {
$upload_dir = wp_upload_dir();
$temp_file = $upload_dir['basedir'] . '/ht_form/temp/' . $file_name;
if($destination === 'media_library') {
// === ATTACH TO MEDIA LIBRARY ===
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/image.php';
$file = [
'name' => basename($file_name),
'tmp_name' => $temp_file,
'type' => mime_content_type($temp_file),
'error' => 0,
'size' => filesize($temp_file),
];
$attachment_id = media_handle_sideload($file, 0);
if(is_wp_error($attachment_id)) {
return $attachment_id->get_error_message();
} else {
@unlink($temp_file);
return wp_get_attachment_url($attachment_id);
}
} elseif($destination === 'ht_form_default') {
$destination = $upload_dir['basedir'] . '/ht_form';
if (!file_exists($destination)) {
wp_mkdir_p($destination);
}
$file_name = wp_unique_filename($destination, $file_name);
$file_path = "$destination/$file_name";
if (rename($temp_file, $file_path)) {
// Return URL instead of file path
return $upload_dir['baseurl'] . '/ht_form/' . $file_name;
}
}
return false;
}
Vulnerability Analysis
Because the file name parameter is not properly validated, attackers can supply arbitrary paths. This input is then passed to the rename() function, which relocates the specified file into the uploads directory.
As a result, an attacker can target and move any file on the server, effectively deleting it from its original location. Even critical files like wp-config.php can be moved by unauthenticated users. Once wp-config.php is removed, WordPress reverts to its installation setup, giving attackers the opportunity to hijack the process by linking the site to a database they control. This enables them to take over the site and potentially escalate access to the server for further exploitation.
Patch Analysis

At Submission.php file, they add an sanitize_file_name() function to patch the issue at handle_files_upload function, with this patch, when user input got in, the function will check the file name to make sure it does not have any suspicious name.
Technical Trace Analysis: From Source to Sink
The following analysis demonstrates the data flow of the Arbitrary File Upload vulnerability leading to Remote Code Execution (RCE) in version 2.2.1.
The Source: Unauthenticated Input Entry
The journey begins when an attacker sends a crafted multipart HTTP POST request to the WordPress AJAX handler.
- Location: Ajax.php
- Method: temp_file_upload()
Code Snippet:
public function temp_file_upload() {
check_ajax_referer('ht_form_ajax_nonce', '_wpnonce');
$file = $_FILES['ht_form_file'];
// Check if file is present
if (!isset($file)) {
wp_send_json_error('No file uploaded.');
}
return $this->file_manager->temp_file_upload($file);
}
Analysis: The variable $_FILES['ht_form_file'] is the Source. It accepts any file provided by the user (e.g., shell.php). Because the check_ajax_referer is the only check performed, any unauthenticated user can reach this point.
The Processing: Logic Bypass & Data Transformation
The data is passed to the FileManager class, which acts as the intermediary processing layer.
- Location: FileManager.php
- Method: temp_file_upload($file)
Logic Flow:
- Directory Definition: It sets the temporary path:
public function temp_file_upload($file) {
$destination = "{$this->dir}/temp";
$this->maybe_create_directories($destination);
- Filename Handling: It calls
$this->process_filename($file['name']);to generate the server-side filename (often adding a prefix).
The Flaw: In version 2.2.1, this method lacks file extension validation (such as wp_check_filetype). The input passes through this layer completely unfiltered, allowing malicious scripts to proceed to the file system.
The Sink: Dangerous File System Execution
This is the “Final Destination” where the malicious input is physically written to the server’s disk.
Location: FileManager.php
Function: move_uploaded_file()
Code Snippet:
// [SINK]
if (move_uploaded_file($file['tmp_name'], $file_path)) {
wp_send_json_success(['file_id' => $filename, ...]);
}
Analysis: This is the Sink. The shell.php file is officially written to the /temp/ directory within the WordPress uploads folder. Since the server-side filename is returned in the JSON response, the attacker can immediately access the file via a web browser to execute commands on the server.

Proof Of Concept (POC)
To demonstrate this vulnerability, I created a WordPress page with the HT-Contactform plugin that includes a file upload function.

First, I uploaded a test.txt file and intercepted the HTTP request using BurpSuite.


When the file was uploaded, the plugin sent a POST request to /wp-admin/admin-ajax.php. The server responded with JSON data containing the process status, file_id, and file_name. I then checked the upload directory to confirm that the file was saved successfully.

The file was stored in:
/wp-content/uploads/ht_form/temp/
From this analysis, it is clear that the current version of the plugin does not implement validation or restrictions on the upload mechanism. To exploit this, I uploaded a PHP shell.

The PHP file contained the following code:
<?php
phpinfo();
?>
The upload was successful. Next, I accessed the file directly to verify execution.

The result was a rendered phpinfo() page, confirming that arbitrary PHP code can be uploaded and executed. This concludes my Proof of Concept for the vulnerability.