SECURITY ADVISORY / 01

CVE-2026-85235 Exploit & Vulnerability Analysis

Complete CVE-2026-85235 security advisory with proof of concept (PoC), exploit details, and patch analysis.

cve_patchdiff:forminator NVD ↗
Exploit PoC Vulnerability Patch Analysis

The Exploit

An unauthenticated attacker can inject a stored XSS payload into a Rich-Text Textarea field; when a site administrator opens the submission in the Forminator Entries view and clicks a crafted link embedded in the stored data, the payload executes in the administrator's session.

Step 1: Store the XSS payload via form submission

POST /index.php?action=forminator_submit_form HTTP/1.1
Host: target.wordpress.local
Content-Type: application/x-www-form-urlencoded

form_id=1&forminator_uid=abc123&forminator_timestamp=1234567890&forminator_nonce=nonce_value&fields[rich_textarea_field]=<a href="&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;alert('XSS')">click me</a>&submit=Submit

The attacker URL-encodes a hyperlink with an entity-encoded javascript: protocol in the href attribute. The form stores this in the database as a Rich-Text Textarea entry. No authentication is required; the form accepts public submissions.

Step 2: Trigger the payload

An administrator logs into wp-admin, navigates to Forminator → Entries, and opens the stored submission containing the injected link. When the admin clicks the link—either intentionally or via social engineering—the entity-encoded href is decoded by the browser's HTML parser and by WordPress jQuery-based click handlers, executing the JavaScript in the admin's authenticated context.

The attacker observes the alert box fire in the administrator's browser, confirming code execution. From there, the attacker can steal session cookies, create backdoor admin accounts, or exfiltrate sensitive form data.


What the Patch Did

Before

function forminator_get_rich_textarea_allowed_html() {
	$allowed_html = wp_kses_allowed_html( 'post' );

	foreach ( $allowed_html as $tag => $attributes ) {
		if ( ! is_array( $attributes ) ) {
			continue;
		}

		if ( isset( $allowed_html[ $tag ]['class'] ) ) {
			$allowed_html[ $tag ]['class'] = array(
				'value_callback' => 'forminator_is_allowed_rich_textarea_class_attribute',
			);
		}
	}

	return $allowed_html;
}

After

function forminator_get_rich_textarea_allowed_html() {
	$allowed_html = wp_kses_allowed_html( 'post' );

	// Validate URL attributes so entity-encoded markup cannot ride in them.
	$url_attributes = wp_kses_uri_attributes();

	foreach ( $allowed_html as $tag => $attributes ) {
		if ( ! is_array( $attributes ) ) {
			continue;
		}

		if ( isset( $allowed_html[ $tag ]['class'] ) ) {
			$allowed_html[ $tag ]['class'] = array(
				'value_callback' => 'forminator_is_allowed_rich_textarea_class_attribute',
			);
		}

		foreach ( $url_attributes as $url_attribute ) {
			if ( isset( $allowed_html[ $tag ][ $url_attribute ] ) ) {
				$allowed_html[ $tag ][ $url_attribute ] = array(
					'value_callback' => 'forminator_is_allowed_rich_textarea_url_attribute',
				);
			}
		}
	}

	return $allowed_html;
}

function forminator_is_allowed_rich_textarea_url_attribute( $url_value ) {
	$decoded = (string) $url_value;

	do {
		$previous = $decoded;
		$decoded  = html_entity_decode( $previous, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
	} while ( $decoded !== $previous );

	if ( preg_match( '/[<>"\'`]/', $decoded ) ) {
		return false;
	}

	return true;
}

The patch introduces a new validation callback function forminator_is_allowed_rich_textarea_url_attribute() and applies it via wp_kses value callbacks to all URL-bearing attributes (href, src, data, etc.) across all HTML tags. The callback decodes entity-encoded characters iteratively (to catch multi-encoded payloads) and rejects any value containing unencoded angle brackets, quotes, or backticks—characters that can break out of HTML and JavaScript sinks.


Root Cause

CWE-79: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

User-supplied content from public form submissions flows into the Rich-Text Textarea field. The plugin's sanitization pipeline uses wp_kses() to allow safe HTML tags and attributes—but wp_kses() preserves HTML entities verbatim. An attacker can entity-encode a malicious protocol (&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58; = javascript:), and it will pass wp_kses() validation. When the stored data is later rendered in the WordPress admin Entries view, client-side code (jQuery, the browser's HTML parser, or inline event handlers) decodes the entities back into a live javascript: link, crossing the trust boundary from a sanitized stored string into executable code.


Why It Works

The load-bearing line is the regex match in forminator_is_allowed_rich_textarea_url_attribute():

if ( preg_match( '/[<>"\'`]/', $decoded ) ) {
    return false;
}

Without this check, entity-encoded dangerous characters would still pass validation. The surrounding while loop that decodes iteratively is equally critical—it ensures that multi-encoded payloads (e.g., &#38;#106; = &#106; = j when double-decoded) cannot bypass the check by encoding the encoded characters themselves. If you removed just the regex line, an attacker could still inject &#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58; and execute it. The iteration loop prevents a second encoding layer from slipping through. Together, they form a gating function: only URL attribute values that decode to safe, literal text are allowed; anything decoding to HTML syntax is rejected before storage, not just before display.


Hardening Checklist

  • Use wp_kses() with explicit value_callback validators for all user-controlled URL-bearing attributes (not just class). Do not rely on wp_kses() alone to reject entity-encoded attack vectors; it preserves entities by design.
  • Implement iterative entity decoding in validators (html_entity_decode() in a loop until the string stabilizes) to catch multi-encoded payloads that single-pass decoding would miss.
  • Test form storage endpoints with entity-encoded XSS payloads (e.g., &#60;script&#62;) and verify they are either rejected at input validation or stripped from the allowlist before the entry is saved.
  • Do not perform wp_unslash() at each recursion level when recursively sanitizing nested arrays; unslash once at the entry point, then call wp_kses() before storage to avoid encoding-layer bypasses.
  • Audit all rich-text and WYSIWYG fields for similar whitelist gaps; apply URL attribute validation consistently across the codebase.

References

  • https://nvd.nist.gov/vuln/detail/CVE-2026-85235

Frequently asked questions about CVE-2026-85235

What is CVE-2026-85235?

CVE-2026-85235 is a security vulnerability. This security advisory provides detailed technical analysis of the vulnerability, exploit methodology, affected versions, and complete remediation guidance.

Is there a PoC (proof of concept) for CVE-2026-85235?

Yes. This writeup includes proof-of-concept details and a technical exploit breakdown for CVE-2026-85235. Review the analysis sections above for the PoC walkthrough and code examples.

How does CVE-2026-85235 get exploited?

The technical analysis section explains the vulnerability mechanics, attack vectors, and exploitation methodology. PatchLeaks publishes this information for defensive and educational purposes.

What products and versions are affected by CVE-2026-85235?

CVE-2026-85235 — check the affected-versions section of this advisory for specific version ranges, vulnerable configurations, and compatibility information.

How do I fix or patch CVE-2026-85235?

The patch analysis section provides guidance on updating to patched versions, applying workarounds, and implementing compensating controls.

What is the CVSS score for CVE-2026-85235?

The severity rating and CVSS scoring for CVE-2026-85235 is documented in the vulnerability details section. Refer to the NVD entry for the current authoritative score.