Contact Form to Any API <= 3.0.3 - Unauthenticated Stored Cross-Site Scripting
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:NTechnical Details
<=3.0.3This 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_saveorwp_ajax_nopriv_cf7_to_api_data(inferred). - Vulnerable Parameter: Individual form field values (e.g.,
your-name,your-message, or a generalizedinput_valuesarray). - Authentication Level: Unauthenticated (No login required).
- Preconditions: The plugin must be active and configured to record/log form submissions.
3. Code Flow (Inferred)
- Entry Point: An unauthenticated user submits a form. The plugin catches this via a
wp_ajax_nopriv_*hook or a globalinithook that checks for specific$_POSTparameters. - Processing: The plugin identifies the form submission. It iterates through the
$_POSTdata. - Storage (Sink): The plugin uses
$wpdb->insert()orupdate_option()to store the raw, unsanitized input into a custom table (e.g.,wp_cf_to_any_api_entries) or the options table. - Retrieval: An administrator navigates to the plugin's "Entries" page in the WP-Admin.
- Output (Sink): The plugin retrieves the stored data and echoes it directly into the HTML table/view without using
esc_html()orwp_kses().
4. Nonce Acquisition Strategy
This plugin acts as a bridge for public contact forms. Public forms typically either:
- Lack Nonces: To ensure compatibility with caching plugins.
- Use External Nonces: Use the nonce provided by the primary form plugin (e.g., CF7).
Strategy for the Agent:
- Scan for AJAX Actions: Run
grep -rn "wp_ajax_nopriv" .to find the submission handler. - Check for Nonce Verification: Look for
check_ajax_refererorwp_verify_nonceinside the identified handler. - Identify Localization: If a nonce is required, search for
wp_localize_scriptto find the JS variable name. - 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_evalto 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
- Install Plugin:
contact-form-to-any-api. - Plugin Configuration: Ensure "Store Entries" or "Logging" is enabled in the plugin settings.
- Create a Target Form: If the plugin requires an existing form to bridge, install
contact-form-7and create a basic form. - Public Page: Place the form on a public page if needed for nonce extraction.
7. Expected Results
- The HTTP request should return a success status (e.g.,
{"success": true}). - 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
- 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)
- HTML Source Check: Use
http_requestas 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_contentorpost_meta. Checkwp 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).
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
@@ -54,7 +54,7 @@ $data = unserialize($entry->form_values); foreach ($data as $key => $value) { - echo "<td>" . $value . "</td>"; + echo "<td>" . esc_html($value) . "</td>"; } @@ -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.