Introduction
A public contact form is supposed to be the one part of a WordPress site that strangers can safely write to. In Ninja Forms versions up to and including 3.15.5, an anonymous visitor could use that same submission path to store script content that later executes in the browser of whoever opens the submission in the WordPress admin, which in practice means site administrators.
Ninja Forms is a WordPress form builder published by Saturday Drive INC (author Kevin Stover; NVD uses the vendor identifier kstover), used for contact forms, lead generation, calculators, quizzes, surveys, applications, event registration and booking requests. The official WordPress plugin directory lists more than 500,000 active installations, and the vendor advertises over 50 million downloads, although cumulative downloads and active installs are different measures and neither tells us how many sites still run an affected version. Because public forms are reachable by unauthenticated visitors by design, weaknesses in the plugin's submission handling sit on the most exposed surface of a large number of WordPress sites.
Technical Information
Classification and scoring
The weakness is classified as CWE-79 (improper neutralization of input during web page generation). The 7.2 High score shown on NVD was supplied by Wordfence as the CNA. NVD's own assessment is listed as unavailable. The published vector is:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N
That rates the issue as network reachable, low complexity, requiring no privileges, with changed scope and low confidentiality and integrity impact. The vector lists user interaction as none, while the description says the injected script runs whenever a user accesses an injected page. Our reading is that the injection itself needs no interaction, but execution depends on someone viewing the affected submission. We report the score as published rather than rescoring it.
The documented impact is arbitrary script execution in the browser of a user viewing affected content. Neither the CVE description nor the Wordfence advisory claims server-side code execution or a confirmed site takeover for this CVE.
Root cause: a reserved key checked before WordPress normalizes it
Ninja Forms lets a submission carry an extra object inside formData. Some keys in that object are meant to be generated by the server, not the client. One of them is calculations, which the admin Calculations metabox later reads and displays.
In 3.15.5, the submission controller (includes/AJAX/Controllers/Submission.php) tried to stop clients from supplying that key, but only removed it when the key matched calculations exactly:
if( isset( $this->_data['extra']['calculations'] ) ) { unset( $this->_data['extra']['calculations']); unset( $this->_form_data['extra']['calculations']); }
WPSec's analysis explains why this is insufficient. A key containing a backslash does not match the literal string calculations, so it survives the check. Later, when the data is persisted, WordPress's update_post_meta() unslashes the meta key, and the stored key becomes calculations. The attacker-controlled value now sits under the same meta key the server would normally populate itself. This mismatch between the key that was validated and the key that was stored is what Wordfence's advisory title calls Metadata Key Normalization.
Persistence path
The Save action (includes/Actions/Save.php) passes supplied extra data through validateExtraData() and then to update_extra_values(). The submission model's extra value methods (includes/Database/Models/Submission.php) accept metadata keys and values and ultimately write them with update_post_meta(). The validation visible in 3.15.5 consists of array and quantity checks. Those checks constrain the shape of the data but do nothing to make its content safe for HTML output.
Rendering sink: an unescaped array key
The Calculations metabox (includes/Admin/Metaboxes/Calculations.php) retrieves the calculations extra metadata and passes it to includes/Templates/admin-metaboxes-calcs.html.php. In the 3.15.5 template, the calculation value is passed through esc_html(), but the array key $name is echoed directly:
<?php foreach( $data as $name => $contents ):?> <li> <strong><?php echo( $name ); ?></strong> <?php echo( ' = ' . esc_html( $contents[ 'value' ] ) );
Array keys are easy to treat as program controlled, which is probably why this one line was missed while the value, raw and parsed strings were all escaped. Here the key is attacker controlled, so any markup placed in a calculation name is emitted as live HTML when an administrator opens the submission.
The nonce does not act as a barrier
The controller registers wp_ajax_nopriv_nf_ajax_submit, so the submission action is reachable without authentication, and it decodes formData from JSON. When nonce validation fails, the controller generates a fresh nonce and returns it, along with a timestamp, in its response. According to the advisory, this effectively neutralizes the nonce check and makes exploitation achievable in a single unauthenticated request. The public sources establish the handler and the nonce disclosure behavior. They do not include a complete request transcript, and we do not reproduce one here.
Attack flow
Based on the advisory, WPSec's explanation and the 3.15.5 source:
- An unauthenticated attacker sends a submission to the
nf_ajax_submitAJAX action for a publicly reachable form. If the nonce is missing or invalid, the response hands back a validnew_nonceandnonce_tspair. - The submission's
formData.extracontains a key that resemblescalculationsbut includes a backslash, carrying markup in the calculation name. - The exact-match check in the controller does not recognize the key, so the entry is not removed.
- The Save action validates only the shape of the extra data and stores it through
update_post_meta(), which unslashes the key tocalculations. - Later, an administrator opens the submission under Ninja Forms, Submissions. The Calculations metabox reads the
calculationsmetadata and echoes the attacker-supplied name without escaping. - The script executes in the administrator's authenticated browser session.
Because forms with no configured calculations never receive server-generated calculation data, a Calculations panel on a submission from such a form is a strong sign of injection. We return to this in the detection section.
Exploitation status
The CVE record includes a CISA assessment listing exploitation as none and automatable as no. TheHackerWire reports no confirmed public exploit code, no CISA KEV listing and no known ransomware linkage. No threat actor is named in any reviewed source. Wordfence credits the researcher daroo with the discovery. These statements describe observed activity at the time of reporting. They say nothing about how difficult the flaw is to exploit.
Patch Information
The fix ships in Ninja Forms 3.15.6, released October 15, 2026. Wordfence lists 3.15.6 as the patched version, and the vendor changelog covers it with a general entry about protecting stored submission data against cross-site scripting. The Trac changeset referenced by NVD and Wordfence (r3749135) blocked automated fetching, so the snippets below come from comparing the 3.15.5 and 3.15.6 tags in the WordPress.org SVN repository. The comparison shows two separate fixes: one where the data is displayed, and one where it comes in.
Fix 1: escape the calculation name on output
In 3.15.6 the template escapes the array key as well:
<?php foreach( $data as $name => $contents ):?> <li> <strong><?php echo( esc_html( $name ) ); ?></strong> <?php echo( ' = ' . esc_html( $contents[ 'value' ] ) );
This change is what stops the script from running. Even if bad data still reaches the database, any script tag or event handler markup in a calculation name is now shown as harmless text when an administrator opens the submission.
Fix 2: scrub reserved keys on input, after normalization
Version 3.15.6 adds a dedicated scrub step at the very start of process() in the submission controller:
protected function process() { // Discard client-submitted extra data stored under a reserved, // server-generated key before it enters processing. See issue #8228. $this->scrubReservedExtraKeys();
The reserved keys are defined in a helper that other code can extend through a filter:
protected function getReservedExtraKeys(): array { return apply_filters( 'ninja_forms_reserved_extra_keys', array( 'calculations', 'product_fields', ) ); }
The scrub then compares each key in the form WordPress will store it:
foreach ( array_keys( $this->_form_data[ 'extra' ] ) as $key ) { if ( in_array( wp_unslash( $key ), $reserved, true ) ) { unset( $this->_form_data[ 'extra' ][ $key ] ); } }
The fix turns on the wp_unslash( $key ) call. The old code checked the key before WordPress transformed it; the new code applies the same unslashing first and then checks, so a slashed variant such as calc\ulations can no longer slip through. The strict in_array( ..., true ) comparison avoids type-juggling surprises. Because the scrub runs inside process(), the code comments indicate it covers both fresh submissions and resumed ones after a PayPal or Stripe redirect. The patch also reserves product_fields, another server-generated key, so it cannot be spoofed the same way.
What the patch does not change
The just-in-time nonce logic in submit() is identical in both versions. When nonce validation fails, the plugin still returns a fresh new_nonce and nonce_ts pair. In 3.15.6 the protection comes from rejecting reserved keys on input and escaping calculation names on output, not from a stricter nonce check. With both changes in place, an attacker can no longer plant a fake calculations entry, and any markup that does reach a calculation name is rendered as plain text.
Detection Methods
As of November 3, 2026, no detection content written specifically for CVE-2026-109997 has been published. There are no Sigma, YARA, Snort/Suricata or WAF signatures. Tenable lists no plugins for this CVE, and isMalicious reports no Nuclei template, Exploit DB entry or Metasploit module, with zero indicators linked to the CVE over the prior seven days. Detection therefore rests on three activities: finding vulnerable installs, finding the stored payload, and spotting what happens after it runs.
Find exposed installs
Any Ninja Forms install at version 3.15.5 or earlier is in scope. Version 3.15.6 is fixed. WPScan and Wordfence Intelligence (which offers a free API and webhook feed) both track the issue, so scanners built on either database should flag affected versions. For a single site, notmyfault.fail suggests this WordPress command-line check:
wp plugin list --name=ninja-forms --fields=name,status,version,update,update_version
The same source makes two further points: an inactive status does not prove a site is safe if the plugin files are still deployed, and exposure should be ranked by how many forms anonymous visitors can reach. It also advises against testing by submitting crafted payloads, since version, configuration and log evidence are enough for triage.
Hunt for the stored payload
TrustBrain's analysis of the 3.15.5 to 3.15.6 diff points to the most reliable signal:
- Submissions from forms that have no calculations configured should never carry calculation data. If a Calculations section appears on one of them, it was very likely injected from outside.
- Look for characters or strings in calculation names that legitimate labels never use, such as
<,scriptoronerror=. - The payload runs when an admin opens the Edit Submission screen, so TrustBrain advises against opening that screen until the plugin is updated. A safer approach is to query stored submission metadata directly for
calculationsentries on forms without calculations.
Web and application log hunting
No vendor has published a log pattern, but the attack path suggests what to look for in access logs and request body logs from a WAF or reverse proxy:
- Endpoint: POST requests to the
nf_ajax_submitAJAX action, especially from unauthenticated clients. - Body content: requests where the
extraportion offormDataholds a key that looks likecalculationsbut contains backslashes, or values with HTML or script-like content. - Context: submissions to forms without calculations that still carry calculation-style extra data.
notmyfault.fail also recommends reviewing recent form submissions, WordPress administrator events and web logs for unexpected changes around public forms. Preserve logs and copies of relevant submissions before making broad changes, and keep in mind that finding nothing suspicious is not proof that exploitation did not occur.
Post-exploitation indicators
Because the script runs in an administrator's browser session, visible damage tends to appear as admin-level actions. TrustBrain specifically warns about new administrator accounts and settings changes. Check the Users list for administrators nobody recognizes, and review audit logs for admin actions shortly after someone viewed a submission's edit screen. Moving the injected submission to trash removes the stored trigger, but only do this after the evidence has been preserved.
Network layer coverage
Generic XSS rules in a WAF may catch obvious script content in form submissions, but every source treats this as best-effort coverage rather than a CVE-specific control or a substitute for patching. Because the bypass relies on a key name that is normalized on save rather than on a particular payload, payload-focused signatures can miss it. The stored data check above is the more dependable control.
Affected Systems and Versions
| Release | Status for CVE-2026-109997 |
|---|---|
| Ninja Forms 3.15.5 and all earlier versions | Affected |
| Ninja Forms 3.15.6 (October 15, 2026) | First patched version per Wordfence |
| Ninja Forms 3.15.7 (November 2, 2026) | Newer release with additional hardening |
Notes on scope:
- The vulnerable configuration is any site running an affected version with forms reachable by unauthenticated visitors. The injection path is the public
nf_ajax_submitaction. - Per notmyfault.fail, an installation where the plugin is inactive but its files remain deployed should still be treated as in scope during triage.
- Version 3.15.7 adds further protections, including merge tag hardening and enforcement of the setting requiring users to be logged in when submitting a form. The changelog does not identify those changes as the fix for this CVE, so a site on 3.15.6 is not affected by CVE-2026-109997 merely because 3.15.7 exists.
- The separate Ninja Forms File Upload add-on is a different product with its own vulnerability history (see below) and is not the subject of this CVE.
Vendor Security History
This is not the first serious Ninja Forms issue. On June 16, 2022, Wordfence reported a 9.8 Critical code injection vulnerability that allowed unauthenticated invocation of plugin methods and object injection, with arbitrary code execution or file deletion depending on a usable POP chain. Wordfence described evidence suggesting exploitation. The response included fixes backported across multiple release branches, and WordPress appeared to have performed a forced automatic update, although Wordfence cautioned that automatic updates do not always succeed.
In 2026, the separate Ninja Forms File Upload add-on was affected by CVE-2026-0740, rated 9.8 Critical, impacting versions through 3.3.26 and fixed in 3.3.27. Wordfence reported exploitation beginning April 6, 2026, and more than 118,600 blocked exploit attempts at the time of its article. That incident shows attacker interest in the Ninja Forms ecosystem, but it does not establish exploitation of CVE-2026-109997. Inventory the core plugin and its add-ons separately. The 3.15.6 core fix and the 3.3.27 File Upload fix address different products and vulnerabilities.
References
- NVD entry for CVE-2026-109997
- Official CVE record (CVE Services API)
- Wordfence vulnerability record (CVE source link)
- Wordfence advisory: Ninja Forms 3.15.5 and earlier, unauthenticated stored XSS via the extra parameter via metadata key normalization
- WPSec advisory WPSEC 2026 1133
- WPScan vulnerability entry
- TrustBrain analysis
- notmyfault.fail: patching Ninja Forms 3.15.6
- isMalicious CVE page
- Tenable plugin coverage for this CVE
- TheHackerWire analysis
- Ninja Forms changelog
- Ninja Forms changelog archive
- Ninja Forms on the WordPress plugin directory
- Trac: AJAX Controllers Submission.php (3.15.5, line 428)
- Trac: AJAX Controllers Submission.php (3.15.5, line 52)
- Trac: Actions Save.php (3.15.5, line 311)
- Trac: Admin Metaboxes Calculations.php (3.15.5, line 23)
- Trac: Database Models Submission.php (3.15.5, line 731)
- Trac: calculations metabox template (3.15.5, line 4)
- Trac: calculations metabox template (3.15.6)
- Trac changeset 3749135
- SVN: calculations metabox template (3.15.5)
- SVN: calculations metabox template (3.15.6)
- SVN: AJAX submission controller (3.15.5)
- SVN: AJAX submission controller (3.15.6)
- Wordfence (2022): critical vulnerability patched in Ninja Forms
- Wordfence (2026): attackers exploiting Ninja Forms File Upload



