CVE-2026-55793Medium▾ SunlitCraft CMS: Stored XSS via Structure entry title in table view
▾ Sunlit zone — Low / medium · no exploitation signal
impact 27.5 · likelihood 0.1 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
Exploit-prediction probability, daily snapshots since Jul 6.
Disclosure to exploitation, from the record and what we observed since indexing it.
Disclosed via GHSA
0.4%
An Author-level control panel user can store a JavaScript payload in an entry title. When an admin, or any control panel user with saveEntries for the same Structure section, drags another entry under the poisoned entry in table view, the payload executes in the victim’s session.
The issue is exploitable because the title is escaped into data-title by the server, decoded again by the browser, read with jQuery .data('title'), and then concatenated into a new HTML string without attribute escaping.
Execution was verified with alert(document.domain). Impact was verified against an elevated admin session by changing the admin user’s email through users/save-user, then using the password-reset flow to take over the account.
createEntries + saveEntries on a Structure-type section (Author-style CP account; no admin permission required).saveEntries on the same section. Craft only renders drag handles when structureEditable is true, which requires this permission, so any admin qualifies.ElementTableSorter.js lines 643-652:
const ancestorTitle = this._updateAncestors._$ancestor.data('title');
$(
'<button … aria-label="' +
Craft.t('app', 'Show {title} children', {title: ancestorTitle}) +
'"></button>'
).insertAfter(…);
Craft.t with a {title} token calls _parseToken, reaches case 'none': return arg (Craft.js:193-194), and returns the title verbatim. The result is handed to jQuery's $() and parsed as HTML.
The server-side template correctly encodes the entry title into data-title, but the browser decodes that attribute before jQuery returns it from .data('title'). At that point the value is attacker-controlled HTML, and it is inserted into the aria-label attribute without Craft.escapeHtml().
Plant (attacker - Author account):
python3 poc.py --url http://target --cp admin \
--user [email protected] --pass secret --section mySection
Trigger (victim - any control panel user with saveEntries, e.g. admin):
alert(document.domain) fires in the victim’s session.
Impact payload tested in an elevated admin session (249 chars, within the 255-char title limit):
"><img src=x onerror="fetch(Craft.actionUrl+'users/save-user',{method:'POST',body:Craft.csrfTokenName+'='+encodeURIComponent(Craft.csrfTokenValue)+'&userId=1&email=attacker%40evil.com',headers:{'Content-Type':'application/x-www-form-urlencoded'}})">
When triggered during an elevated admin session, the admin’s email is changed to the attacker-controlled address. The attacker can then request a password reset and receive the reset link.
Stored XSS in the control panel from an Author-level account. The payload runs as the victim control panel user and can use Craft.csrfTokenName / Craft.csrfTokenValue to send same-origin action requests as that user.
In my test environment, triggering the payload in an elevated admin session allowed Author-to-admin account takeover via admin email change and password reset.
https://github.com/craftcms/cms/commit/162321e899cc97517fb6f5a02b5528f549d0c6cc
craftcms/cms >= 5.0.0-RC1, < 5.9.22Upgrade to a patched release:
craftcms/cms 5.9.53Connected by shared product, vendor, weakness, or advisory.
GHSA-2rp4-x2j7-qmccMediumCraft CMS: Stored XSS in the control panel via unescaped draft name
CVE-2026-55790HighCraft CMS: DOM XSS via GitHub issue title in CraftSupport widget
GHSA-xxpx-f366-4xpqMediumCraft CMS:Authorization bypass: view-only Categories user can modify category structure via structures/move-element
GHSA-wg23-69c2-gjc8CriticalCraft CMS: Passkey login accepts replayed WebAuthn assertions
GHSA-p8x7-9vfw-p7vcHighCraft CMS: Arbitrary user password reset leading to administrator account takeover
GHSA-7hxc-f267-h5q7LowCraft CMS: Incorrect path validation could potentially lead to path traversal