CVE-2026-56393 Overview
CVE-2026-56393 is a stored cross-site scripting (XSS) vulnerability affecting Craft CMS versions 4.x (>= 4.0.0-RC1, < 4.17.0-beta.1) and 5.x (>= 5.0.0-RC1, < 5.9.0-beta.1). The flaw exists in multiple control-panel templates that render administrator-supplied settings and field option labels without HTML sanitization. The checkbox.twig and radio.twig templates used the Twig |raw filter, allowing arbitrary JavaScript to execute in other users' control-panel sessions. Exploitation requires authenticated administrator access with allowAdminChanges enabled. The vulnerability is tracked under [CWE-79] and was fixed in 4.17.0-beta.1 and 5.9.0-beta.1.
Critical Impact
An authenticated administrator can inject persistent JavaScript payloads into section, volume, user group, global set, field, and option labels, executing in any administrator's browser session.
Affected Products
- Craft CMS 4.x (>= 4.0.0-RC1, < 4.17.0-beta.1)
- Craft CMS 5.x (>= 5.0.0-RC1, < 5.9.0-beta.1)
- Craft CMS control panel components rendering label content via {{ label|raw }}
Discovery Timeline
- 2026-06-21 - CVE-2026-56393 published to NVD
- 2026-06-22 - Last updated in NVD database
Technical Details for CVE-2026-56393
Vulnerability Analysis
The vulnerability stems from unsafe rendering of user-controlled label content across the Craft CMS control panel. Twig templates including src/templates/_includes/forms/checkbox.twig and src/templates/_includes/forms/radio.twig used the |raw filter, which bypasses Twig's default HTML escaping. An administrator with allowAdminChanges enabled can save arbitrary HTML and JavaScript into settings names, volume names, user group names, global set names, generated field names, checkbox and radio option labels, and custom source labels. The stored payload executes whenever another administrator loads a page rendering that label.
Root Cause
The root cause is improper neutralization of input during web page generation [CWE-79]. Templates rendered label values with {{ label|raw }}, instructing Twig to emit the value without escaping HTML special characters. Additionally, src/fields/BaseOptionsField.php concatenated option labels for display without calling Html::encode(), and src/web/assets/cp/src/js/BaseElementIndex.js removed an escapeHtml() call on option labels in element index sources.
Attack Vector
An attacker with administrator privileges navigates to a settings page that accepts a name or label field, then submits a payload such as <script>...</script> within the value. The payload is persisted to the database. When any control-panel user views a page that renders that label, the script executes in their authenticated session, enabling session theft, control-panel actions on their behalf, or further account takeover.
// Patch in src/fields/BaseOptionsField.php
$labels[] = array_pop($options)['label'];
}
+ $labels = array_map(fn($label) => Html::encode($label), $labels);
+
return implode(', ', $labels);
}
Source: Craft CMS commit 67780a7
{# Patch in src/templates/_includes/forms/checkbox.twig #}
<div class="color-preview" style="background-color: {{ color }}"></div>
</div>
{% endif %}
- <span>{{ label|raw }}</span>
+ <span>{{ label }}</span>
</div>
{% else %}
- {{ label|raw }}
+ {{ label }}
{% endif %}
Source: Craft CMS commit 67780a7
{# Patch in src/templates/_includes/forms/radio.twig #}
{{ tag('input', inputAttributes) }}
{% tag 'label' with containerAttributes %}
- {{ label|raw }}
+ {{ label }}
{% endtag %}
Source: Craft CMS commit 943152d
// Patch in src/web/assets/cp/src/js/BaseElementIndex.js
return o.optgroup
? o
: {
- label: Craft.escapeHtml(o.label),
+ label: o.label,
value: o.attr,
};
}),
Source: Craft CMS commit 943152d
Detection Methods for CVE-2026-56393
Indicators of Compromise
- Section, volume, user group, or global set names containing HTML tags such as <script>, <img onerror=...>, or <svg onload=...>
- Field option labels (checkbox, radio, dropdown) containing angle brackets or JavaScript event handlers
- Unexpected outbound HTTP requests from administrator browsers shortly after loading control-panel pages
- Database rows in craft_fields, craft_sections, craft_volumes, or craft_usergroups with non-printable or markup characters in name columns
Detection Strategies
- Query the Craft CMS database for label and name fields containing <, >, script, or on[a-z]+= patterns
- Audit administrator action logs for changes to section, volume, user group, global set, and field configuration entries
- Review web server logs for control-panel POST requests with suspicious payloads in name or label parameters
Monitoring Recommendations
- Monitor accounts holding administrator privileges and any change to allowAdminChanges in config/general.php
- Alert on creation or modification of fields, sections, and option labels by non-standard administrator accounts
- Enable a Content Security Policy (CSP) in report-only mode to surface inline script execution attempts in the control panel
How to Mitigate CVE-2026-56393
Immediate Actions Required
- Upgrade Craft CMS to 4.17.0-beta.1 or 5.9.0-beta.1 (or later stable releases incorporating these fixes)
- Audit existing field, section, volume, user group, and global set names for embedded HTML or script content and remediate any malicious values
- Restrict administrator accounts and disable allowAdminChanges in production environments where it is not required
- Rotate administrator session cookies and credentials if injected payloads are discovered
Patch Information
Craft CMS resolved the issue by replacing {{ label|raw }} with default-escaped {{ label }} in checkbox.twig and radio.twig, reintroducing Craft.escapeHtml() on element index source labels in BaseElementIndex.js, and applying Html::encode() to concatenated option labels in BaseOptionsField.php. Patch details are available in the Craft CMS GHSA-4mgv-366x-qxvx security advisory and the VulnCheck advisory on Craft CMS.
Workarounds
- Set allowAdminChanges to false in production config/general.php to prevent further injection via settings UI
- Limit administrator role assignments to a minimal trusted set of users until patching is complete
- Deploy a strict Content Security Policy that disallows inline scripts for control-panel routes
# Configuration example: disable admin changes in production
# config/general.php
return [
'*' => [
// ... other settings
],
'production' => [
'allowAdminChanges' => false,
],
];
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

