CVE-2026-32460

Ultra Addons for Contact Form 7 <= 3.5.36 - Authenticated (Contributor+) Stored Cross-Site Scripting

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
3.5.37
Patched in
6d
Time to patch

Description

The Ultra Addons for Contact Form 7 plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 3.5.36 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with contributor-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=3.5.36
PublishedMarch 14, 2026
Last updatedMarch 19, 2026

What Changed in the Fix

Changes introduced in v3.5.37

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: Ultra Addons for Contact Form 7 Stored XSS (CVE-2026-32460) ## 1. Vulnerability Summary The **Ultra Addons for Contact Form 7** plugin (up to 3.5.36) contains a stored cross-site scripting (XSS) vulnerability in its option import functionality. The vulnerability exists because the …

Show full research plan

Research Plan: Ultra Addons for Contact Form 7 Stored XSS (CVE-2026-32460)

1. Vulnerability Summary

The Ultra Addons for Contact Form 7 plugin (up to 3.5.36) contains a stored cross-site scripting (XSS) vulnerability in its option import functionality. The vulnerability exists because the AJAX handler uacf7_option_import_callback fails to properly validate the user's capabilities (due to an incorrect use of is_admin()) and subsequently stores unsanitized JSON data into post meta. This data is then rendered on the frontend or backend without sufficient escaping.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: uacf7_option_import (hooked via wp_ajax_uacf7_option_import)
  • Vulnerable Parameter: tf_import_option (JSON string)
  • Required Authentication: Contributor or higher.
  • Privilege Escalation Mechanism: The plugin attempts to restrict the action to administrators but uses the following check:
    if ( $current_user_role !== 'administrator' && ! is_admin() ) {
        wp_die( 'You do not have sufficient permissions to access this page.' );
    }
    
    In the context of an AJAX request (admin-ajax.php), is_admin() always returns true. Therefore, the condition $current_user_role !== 'administrator' && false always evaluates to false, allowing any authenticated user with access to the admin dashboard (like a Contributor) to bypass the check.

3. Code Flow

  1. Entry Point: UACF7_Options::__construct() registers the AJAX action:
    add_action( 'wp_ajax_uacf7_option_import', array( $this, 'uacf7_option_import_callback' ) );
  2. Nonce Verification: uacf7_option_import_callback() verifies the nonce tf_options_nonce.
  3. Authorization Bypass: The function performs the flawed role check described above.
  4. Data Processing:
    • $imported_data is created by json_decode( wp_unslash( trim( $_POST['tf_import_option'] ) ), true );.
    • $form_id is retrieved from $_POST['form_id'].
  5. Sink: The unsanitized array is saved to the database:
    update_post_meta( $form_id, 'uacf7_form_opt', $imported_data );
  6. Rendering: When the contact form (ID: $form_id) is rendered, the plugin retrieves uacf7_form_opt and outputs values (likely in a style block or wrapper div) without proper escaping.

4. Nonce Acquisition Strategy

The nonce tf_options_nonce is localized into the JavaScript variable tf_options_params. This occurs in admin/tf-options/TF_Options.php within the tf_options_admin_enqueue_scripts method.

Strategy:

  1. Log in as a Contributor.
  2. The script is enqueued on several admin screens, including the Contact Form 7 list page (toplevel_page_wpcf7).
  3. Navigate to /wp-admin/admin.php?page=wpcf7.
  4. Execute browser_eval to extract the nonce:
    browser_eval("window.tf_options_params?.tf_options_nonce")

5. Exploitation Strategy

  1. Preparation: Identify an existing Contact Form 7 form ID (e.g., 123). If none exist, create one.
  2. Payload Crafting: Create a JSON object where values contain XSS payloads. Since this meta is for "Form Options" (styling), common fields likely include font families or custom CSS.
    • payload = {"uacf7_custom_css": "</style><script>alert(document.domain)</script>"}
  3. Request: Send a POST request to admin-ajax.php.
    • Action: uacf7_option_import
    • ajax_nonce: [Extracted Nonce]
    • form_id: 123
    • tf_import_option: {"any_key": "<img src=x onerror=alert(1)>"}
  4. Verification: View the page where Contact Form 123 is embedded.

6. Test Data Setup

  1. Install Plugins: Contact Form 7 and Ultra Addons for Contact Form 7 (v3.5.36).
  2. Create User: A user with the contributor role.
  3. Create Form: A Contact Form 7 form. Note its ID (e.g., wp-post-create or check the UI).
  4. Create Page: A public page containing the shortcode: [contact-form-7 id="123" title="Test Form"].

7. Expected Results

  • The AJAX request should return a JSON success message: {"success":true,"data":{"status":"success","message":"Options imported successfully!"}}.
  • The database should show the payload stored in the wp_postmeta table for the specified post_id under the key uacf7_form_opt.
  • When visiting the public page, an alert box should trigger.

8. Verification Steps (WP-CLI)

Check if the meta was successfully injected:

wp post meta get [FORM_ID] uacf7_form_opt --format=json

This should return the JSON object containing the <script> or <img> tag.

9. Alternative Approaches

If the tf_options_nonce is not localized on the main Contact Form 7 page for Contributors, try navigating to:

  • /wp-admin/admin.php?page=uacf7_settings
  • /wp-admin/admin.php?page=uacf7_setup-wizard

If a specific key is required in the JSON for it to be rendered, analyze admin/tf-options/metaboxes/ or options/ files to find valid option keys used by the plugin (e.g., form-title-color, form-background-color). A reliable fallback is to inject into every key:
{"key1": "<script>alert(1)</script>", "key2": "<script>alert(1)</script>" ...}

Research Findings
Static analysis — not yet PoC-verified

Summary

The Ultra Addons for Contact Form 7 plugin is vulnerable to Stored Cross-Site Scripting via the 'uacf7_option_import' AJAX action due to a flawed capability check and lack of output escaping. Authenticated attackers with Contributor-level access can bypass the intended administrator-only restriction and inject malicious scripts into contact form metadata, which then executes when the form is viewed.

Vulnerable Code

// admin/tf-options/TF_Options.php:64
		public function uacf7_option_import_callback() {

			if ( ! isset( $_POST['ajax_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['ajax_nonce'] ) ), 'tf_options_nonce' ) ) {
				return;
			}

			//  Checked Currenct can save option
			$current_user = wp_get_current_user();
			$current_user_role = $current_user->roles[0];

			if ( $current_user_role !== 'administrator' && ! is_admin() ) {
				wp_die( 'You do not have sufficient permissions to access this page.' );
			}

			$imported_data = json_decode( wp_unslash( trim( $_POST['tf_import_option'] ) ), true );
			$form_id = stripslashes( $_POST['form_id'] );

			$response = [ 
				'status' => 'error',
				'message' => __( 'Something went wrong!', 'ultimate-addons-cf7' ),
			];

			if ( ! empty( $imported_data ) && is_array( $imported_data ) ) {
				if ( $form_id != 0 ) {

					update_post_meta( $form_id, 'uacf7_form_opt', $imported_data );

				}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/ultimate-addons-for-contact-form-7/3.5.36/admin/tf-options/TF_Options.php /home/deploy/wp-safety.org/data/plugin-versions/ultimate-addons-for-contact-form-7/3.5.37/admin/tf-options/TF_Options.php
--- /home/deploy/wp-safety.org/data/plugin-versions/ultimate-addons-for-contact-form-7/3.5.36/admin/tf-options/TF_Options.php	2026-02-03 18:51:00.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/ultimate-addons-for-contact-form-7/3.5.37/admin/tf-options/TF_Options.php	2026-02-26 15:44:42.000000000 +0000
@@ -64,12 +64,10 @@
 				return;
 			}
 
-			//  Checked Currenct can save option
-			$current_user = wp_get_current_user();
-			$current_user_role = $current_user->roles[0];
-
-			if ( $current_user_role !== 'administrator' && ! is_admin() ) {
-				wp_die( 'You do not have sufficient permissions to access this page.' );
+			if ( ! current_user_can( 'manage_options' ) ) {
+				wp_send_json_error(
+					[ 'message' => __( 'You do not have sufficient permissions to access this page.', 'ultimate-addons-cf7' ) ]
+				);
 			}
 
 			$imported_data = json_decode( wp_unslash( trim( $_POST['tf_import_option'] ) ), true );

Exploit Outline

The exploit targets the 'uacf7_option_import' AJAX action. An authenticated user (Contributor+) can bypass the permission check because it incorrectly uses is_admin() within an AJAX context, where it always returns true. The attacker extracts the 'tf_options_nonce' from the WordPress admin dashboard scripts and sends a POST request to admin-ajax.php. This request includes a 'tf_import_option' JSON payload containing a malicious script and a target 'form_id'. The plugin saves this unsanitized JSON data into the 'uacf7_form_opt' post meta, which triggers the XSS payload whenever the associated contact form is rendered on the site.

Check if your site is affected.

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