Jul 7, 2026·8 min read·49 visits
A DOM-based Stored XSS in Craft CMS (5.0.0-RC1 to 5.9.22) allows low-privileged authors to execute arbitrary JavaScript in admin sessions when admins perform drag-and-drop operations on affected entry structures.
CVE-2026-55793 is a DOM-based Stored Cross-Site Scripting (XSS) vulnerability affecting Craft CMS versions 5.0.0-RC1 through 5.9.22. An authenticated user with minimum Author privileges can store a malicious payload in an entry's title. When an administrator or high-privileged user performs a drag-and-drop operation under the modified entry in the structure table view, the unescaped payload is retrieved and concatenated into raw HTML, resulting in arbitrary JavaScript execution within the context of the administrative session.
Craft CMS incorporates a dynamic structure management interface allowing content editors to establish hierarchical relationships between different entries. This system operates using an interactive drag-and-drop mechanism managed on the client side by the Garnish UI library and Craft's ElementTableSorter.js module. Under normal operations, users with appropriate entry creation permissions can organize entries into complex trees containing multiple levels of depth.
This dynamic hierarchy relies on client-side state tracking to determine when structural elements, such as expand and collapse toggles, need to be created or updated. When an element is modified inside the table view, the front-end application dynamically reconstructs the associated toggle buttons. This action relies on metadata stored in the HTML document to populate accessibility strings, specifically constructing labels to inform screen readers which children are being manipulated.
The attack surface for this vulnerability is exposed through entry creation and modification interfaces. Users with low-privileged access, such as the Author role, can save entries containing custom titles. While the server performs routine HTML escaping before outputting the structural table view, client-side scripts extract and deserialize these values, introducing an indirect boundary transit from untrusted database storage to dynamic execution sinks.
The root cause of CVE-2026-55793 resides in the unsafe programmatic pattern of dynamic HTML string concatenation followed by jQuery evaluation. When Craft CMS serves the entry table view, the server-side rendering engine translates database values into DOM elements, encoding raw characters into HTML entities. For instance, an entry title containing special characters is rendered inside a table row's dataset attribute: <tr data-title=""><script>alert(1)</script>">.
When the browser parses this server-rendered HTML, it automatically decodes the entities during DOM initialization. Consequently, the literal, unescaped payload string resides in the memory space of the DOM object. Inside ElementTableSorter.js, when a drag-and-drop event modifies the position of a row, the script checks if the parent node requires a toggle button. The script queries the parent table row's attributes using the jQuery .data('title') API, which extracts this unescaped string directly from memory.
The script then invokes the localization translation function Craft.t('app', 'Show {title} children', {title: ancestorTitle}). This function interpolates the unescaped title string into the broader UI translation block without performing any context-aware escaping. The translated string is subsequently concatenated directly into a raw HTML template string representing a button element, preserving the raw, unneutralized quotes and bracket characters of the attacker's original payload.
Finally, the concatenated string is passed directly into the jQuery selector function $(). Because the argument starts with a < character, jQuery recognizes it as markup and passes it to internal parsing utilities such as innerHTML or document.createElement. During this translation into active DOM elements, the browser evaluates the attacker-controlled HTML attributes, breaking out of the designated attribute context and executing the nested scripting code.
To understand the vulnerability mechanics, analyze the code before and after the patch inside src/web/assets/cp/src/js/ElementTableSorter.js. The vulnerable implementation used dynamic string concatenation to construct DOM elements:
// Vulnerable implementation prior to version 5.9.23
if (this._updateAncestors._$ancestor.data('descendants') == 1) {
// Create its toggle
const ancestorTitle = this._updateAncestors._$ancestor.data('title');
$(
'<button class="toggle expanded" type="button" aria-expanded="true" title="' +
Craft.t('app', 'Show/hide children') +
'" aria-label="' +
Craft.t('app', 'Show {title} children', {title: ancestorTitle}) +
'"></button>'
).insertAfter(
this._updateAncestors._$ancestor.find('> th .move:first')
);
}In this vulnerable block, the value of ancestorTitle is directly placed within the double quotes of the aria-label attribute inside the raw HTML string. If an attacker passes a value containing a double-quote character, it prematurely terminates the attribute definition and introduces secondary attributes or complete HTML tags into the parser.
To address this issue, the patch modifies how the button is built, switching to a structured jQuery element declaration signature:
// Patched implementation in version 5.9.23
if (this._updateAncestors._$ancestor.data('descendants') == 1) {
// Create its toggle
const ancestorTitle = this._updateAncestors._$ancestor.data('title');
$('<button/>', {
class: 'toggle expanded',
type: 'button',
'aria-expanded': 'true',
title: Craft.t('app', 'Show/hide children'),
'aria-label': Craft.t('app', 'Show {title} children', {
title: ancestorTitle,
}),
}).insertAfter(
this._updateAncestors._$ancestor.find('> th .move:first')
);
}This revised construction pattern passes the element tag name as the first argument, and all properties and attributes inside an object configuration as the second argument. When jQuery processes this configuration, it instantiates an empty button element and updates the properties programmatically using native DOM APIs such as Element.setAttribute() and Node.textContent. This treats all dictionary values strictly as literal string parameters, preventing any embedded HTML character sequences from compiling into executable instructions.
This remediation is highly complete because it eliminates the possibility of string breakout. Even if the translation helper interpolates a malicious payload, the browser interprets the resulting text strictly as a flat string value within the aria-label attribute of the element, rendering the XSS attack vector obsolete.
Exploitation of CVE-2026-55793 requires a multi-stage workflow involving configuration manipulation, specific payload design, and targeted user interaction. An attacker must first authenticate with an account holding sufficient permissions to write or update entry titles within a designated Structure section.
The attacker crafts a payload designed to break out of the button's attribute wrapper and register an active event handler. Because the vulnerable code wraps the translation within double quotes, the payload begins with a double quote to close the aria-label parameter, followed by an execution trigger. A standard, highly reliable proof-of-concept payload utilizes the autofocus and onfocus parameters:
x" autofocus onfocus="fetch('/admin/actions/users/save-user',{method:'POST',body:new URLSearchParams({'userId':1,'admin':1})})" x="Once the payload is saved as the title of an entry, the attacker positions this entry within the hierarchical table structure view. The exploit will not execute automatically when users simply browse or view the entry list. Instead, the attacker must wait for an administrator or another user with elevated privileges (specifically, holding saveEntries capabilities) to perform a structural reorganization.
When the victim drags an unrelated entry row and places it directly underneath the attacker's poisoned entry, ElementTableSorter.js evaluates the update. Because the poisoned entry transitions to having a child, the script extracts the unescaped payload via .data('title'), concatenates it into the HTML string, and dynamically instantiates the button element inside the DOM. The browser immediately processes the autofocus attribute, focusing the newly rendered button and running the arbitrary JavaScript payload within the security context of the victim's administrative session.
The impact of a successful DOM-based Stored XSS vulnerability in a content management platform is severe, as the payload executes with the privileges of the active session. Because the target of this vulnerability must be a user capable of reorganizing and saving structural configurations, the victim is almost always an administrative user or a high-level content manager.
Using the administrative context, an attacker can perform silent, background administrative tasks. This includes executing requests to register a new admin account, changing existing user credentials, altering system configuration settings, or downloading database backups. Because Craft CMS allows administrators to install plugins and edit templates, the attacker can leverage the compromised session to upload custom templates containing PHP web shells, effectively elevating the DOM-based XSS into an unauthenticated Remote Code Execution (RCE) vulnerability on the underlying web server.
Despite this high integrity compromise, the CVSS v4.0 score is designated as 5.9 (Medium) primarily due to operational limitations. The vulnerability cannot be triggered via simple automated crawler sweeps or passive page visits. It requires an authenticated actor to plant the payload and active, physical administrative interactions to trigger the execution flow, reducing the probability of widespread worm-like propagation.
The definitive remediation for CVE-2026-55793 is to upgrade the Craft CMS application suite to version 5.9.23 or higher. This update replaces the vulnerable dynamic HTML generation pattern with structured jQuery element construction in the ElementTableSorter.js component, neutralizing any attempts at markup injection.
If immediate patching is not possible, organizations should deploy temporary administrative mitigations to reduce the vulnerability footprint. This includes auditing user permissions to ensure that only highly trusted users hold the capability to create or edit entries inside Structure sections. Furthermore, access to table views with hierarchical drag-and-drop structures should be monitored, and standard users should have their sorting permissions revoked where feasible.
In addition, security operations teams should deploy Web Application Firewall (WAF) signatures to intercept and analyze payload submission patterns. Requests hitting the /admin/actions/entries/save-entry routing handler should be inspected for characters typical of attribute breakout patterns, such as matching pairs of double quotes followed by focus, load, or error event handlers.
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:P/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
cms craftcms | >= 5.0.0-RC1, <= 5.9.22 | 5.9.23 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-79 |
| Attack Vector | Network |
| CVSS Base Score | 5.9 (Medium) - CVSS v4.0 |
| EPSS Score | 0.00412 |
| Impact | High Integrity Compromise (Session Takeover / Privilege Escalation) |
| Exploit Status | Proof-of-Concept / Technical Analysis Available |
| CISA KEV Status | Not Listed |
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.
A critical vulnerability in Dgraph Alpha allows unauthenticated network clients to delete and replace database stores. The public gRPC interface on port 9080 processes external snapshot streams without enforcing authentication or authorization, triggering immediate database destruction via the storage engine's initialization process.
A security vulnerability in Copier versions 9.5.0 through 9.15.1 allows unauthenticated remote code execution via crafted HTTP requests or local paths containing traversal sequences. The trust-evaluation mechanism compares target repository paths or URLs against trusted prefixes using unnormalized string comparison, while the subsequent fetching mechanism normalizes the path before cloning. Attackers can exploit this asymmetry to bypass security warning prompts and execute arbitrary commands under the local user context.
GeoLens before version 1.2.4 contains multiple critical-tier security vulnerabilities including improper authorization in metadata access, tile cache scope leakage, dataset title enumeration, weak default credentials, and denial of service via STAC POST search.
CVE-2026-55694 is a chained Information Disclosure and Insecure Direct Object Reference (IDOR) vulnerability in Snipe-IT prior to version 8.6.3. The vulnerability allows authenticated, restricted users to completely bypass randomized file-naming security controls, leak the obfuscated filenames of signed End User License Agreements (EULAs), and subsequently download these confidential documents across tenant boundaries.
Snipe-IT is an IT asset/license management system. Prior to 8.6.3, any activated account can request /maintenances/{id} and read maintenance records for assets in the same company without asset or maintenance permission. app/Http/Controllers/MaintenancesController.php show() renders the record without authorize(), while company-scoped route-model binding only prevents access to other companies. Disclosed fields include asset tags, suppliers, purchase costs, notes, and dates. This issue is fixed in version 8.6.3.
A Stored DOM-based Cross-Site Scripting (DOM XSS) vulnerability exists in Snipe-IT versions prior to 8.6.2. The vulnerability occurs when a stored manufacturer or supplier name is converted to CamelCase and rendered within the 'data-selected-count-id' attribute of a table. Client-side JavaScript retrieves this decoded attribute and performs unsafe string concatenation, passing it directly into jQuery's '.after()' method, enabling authenticated attackers to execute arbitrary JavaScript in the victim's session.