Where
-Infinity
0
Code Injection
AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:L

Impact

This is an additional fix for https://github.com/craftcms/cms/security/advisories/GHSA-4w8r-3xrw-v25g

This is a high-impact, low-complexity attack vector. To mitigate the issue, users running Craft installations before the fixed versions are encouraged to update to at least that version.

Details

https://craftcms.com/knowledge-base/craft-cms-cve-2025-32432

References

https://github.com/craftcms/cms/commit/e1c85441fa47eeb7c688c2053f25419bc0547b47

https://github.com/craftcms/cms/blob/3.x/CHANGELOG.md#3915---2025-04-10-critical https://github.com/craftcms/cms/blob/4.x/CHANGELOG.md#41415---2025-04-10-critical https://github.com/craftcms/cms/blob/5.x/CHANGELOG.md#5617---2025-04-10-critical

https://sensepost.com/blog/2025/investigating-an-in-the-wild-campaign-using-rce-in-craftcms/

Credits

Credit to Orange Cyberdefense for discovering a reporting this bug.

1 / 3
Source: GitHub
First published (updated )
Severity
9.8
OS Command Injection, Code Injection
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Impact You are affected if your php.ini configuration has registerargcargv enabled.

Patches Update to 3.9.14, 4.13.2, or 5.5.2.

Workarounds If you can't upgrade yet, and registerargcargv is enabled, you can disable it to mitigate the issue.

1 / 3
Source: GitHub
First published (updated )
Severity
9.3
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N

Craft CMS 5.0.0-RC1 through 5.10.5 contains an incorrect authorization vulnerability. A control-panel user holding only the viewCategories permission (without saveCategories) for a category group can permanently modify that group's category structure — reordering and re-parenting categories — via the structures/move-element action. The structureEditable flag is computed from the view permission rather than the save permission, and the StructuresController authorizes the mutating action on that read-time session grant without a save re-check. Because a category's URI is derived from its position in the structure, moving a category changes its URL and those of its descendants and can corrupt navigation menus built from the category taxonomy. The issue is fixed in 5.10.6.

First published (updated )
Severity
8.7
Code Injection
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:L/SI:L/SA:L/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Requirements:

Control panel access Permissions to edit an entry

Details

Control panel users with the ability to edit entries can execute unsandboxed Twig code via the HTTP Referrer header.

The issue happens when a user is saving entries. Strings for a signed redirect URL are being compiled as a Twig template via renderObjectTemplate(), and while a sandboxed alternative already exists (renderSandboxedObjectTemplate()), it is not used in this case. This signed URL can be specified by users, as it is reflected in the “Referer” HTTP request header, which is under attacker control.

This has been fixed in Craft 5.10.0. Affected users should update to that version or higher to get the fix.

Resources

https://github.com/craftcms/cms/pull/18680

1 / 2
Source: GitHub
First published (updated )
Severity
8.7
CSRF
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Craft CMS versions from 4.0.0-RC1 before 4.18.2 and from 5.0.0-RC1 before 5.10.6 contain an authenticated remote code execution vulnerability in the control panel element-search condition handling. Craft cleanses the outer request-controlled condition array via Component::cleanseConfig(), but Conditions::createCondition() later decodes and merges the JSON string in condition.config without re-running cleanseConfig() on the decoded configuration. Because condition.config is a JSON string during the first cleanse, Yii special config keys such as 'as ...' and 'on ...' can be hidden inside it and, after JSON decoding, are interpreted by Yii as behavior/event configuration during FieldLayout object creation. An attacker with an authenticated control panel session (and a valid CSRF token) can exploit this to execute operating system commands as the PHP/web user.

First published (updated )
Severity
8.7
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Craft CMS versions >= 5.0.0-RC1 before 5.10.7 and >= 4.0.0-RC1 before 4.18.3 contain a remote code execution vulnerability in the Twig sandbox mechanism. Because Craft marks the ElementInterface as safe (via the AllowedInSandbox attribute) and the sandbox allowlisting extends to the entire class hierarchy (craft\base\Component up to yii\base\Component), an authenticated attacker with permission to access the control panel can render a malicious Twig template that abuses the yii\base\Component arbitrary function-call gadget to execute arbitrary code, even when the Twig sandbox is enabled via enableTwigSandbox().

First published (updated )
Severity
8.7
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Craft CMS versions from 4.0.0-RC1 before 4.18.2 and from 5.0.0-RC1 before 5.10.6 contain an authenticated remote code execution vulnerability in control panel element-search condition handling. A JSON cleanse bypass in condition.config allows Yii behavior/event configuration keys to be interpreted after decoding, enabling command execution as the PHP/web user.

First published (updated )
Severity
8.1
EPSS
0.04%
Code Injection
AV:N/AC:H/PR:L/UI:R/S:C/C:H/I:H/A:H

Impact

This is an RCE vulnerability that affects Craft 4 and 5 installs where your security key has already been compromised.

https://craftcms.com/knowledge-base/securing-craft#keep-your-secrets-secret

Anyone running an unpatched version of Craft with a compromised security key is affected.

Patches

This has been patched in Craft 5.5.8 and 4.13.8.

Workarounds

If you can't update to a patched version, then rotating your security key and ensuring its privacy will help to migitgate the issue.

References

https://github.com/craftcms/cms/commit/e59e22b30c9dd39e5e2c7fe02c147bcbd004e603

1 / 3
Source: GitHub
First published (updated )
Severity
7.6
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

EntriesController::actionSaveEntry() performs entry-edit permission checks before request-controlled author changes are applied to the model. The subsequent author mutation path accepts attacker-supplied authors / author parameters and allows the change when the current user is one of the old authors. Because the controller does not re-run authorization after mutating the author list, a low-privileged user can reassign an entry’s authorship to another user without holding the dedicated peer-author-change permission.

Details The control flow begins in EntriesController.php:249. actionSaveEntry() loads the entry and enforces edit permissions before calling populateEntryModel():

php public function actionSaveEntry(bool $duplicate = false): ?Response { ... $entry = $this->editableEntry($this->request->getBodyParam('entryId'), $siteId); ... $this->enforceEditEntryPermissions($entry, $duplicate); ... $this->populateEntryModel($entry); ... $success = Craft::$app->getElements()->saveElement($entry); }

The attacker-controlled source is in EntriesController.php:588:

php $entry->setAttributesFromRequest(arrayfilter([ 'authorIds' => $this->request->getBodyParam('authors') ?? $this->request->getBodyParam('author') ?? $entry->getAuthorId() ?? static::currentUser()->id, ]));

Entry::setAttributesFromRequest() in Entry.php:1124 extracts the new author IDs and applies them if canChangeAuthor() returns true:

php if ( ($authorIds !== null || $authorId !== null) && $this->canChangeAuthor() ) { $this->oldAuthorIds = $oldAuthorIds; $this->setAuthorIds($authorIds); }

canChangeAuthor() at Entry.php:2789 allows the author change when the current user can view peer entries and is already one of the existing authors:

php return ( empty($authorIds) || inarray($user->id, $authorIds) || $user->can("changeAuthorForPeerEntries:$section->uid") );

After the author list is mutated, the controller does not re-check authorization.

This closes the exploit chain:

1. External source: authenticated request to entries/save-entry with attacker-controlled authors[]. 2. Trust boundary failure: authorization is checked on the pre-mutation entry state, not on the post-mutation author assignment. 3. Privileged sink: the author relationship is rewritten in persistent storage.

Preconditions derived from the source:

1. The attacker is authenticated and can edit entry 345. 2. The attacker is among the existing authors of entry 345, or otherwise satisfies canChangeAuthor() through the old author set. 3. The attacker has viewPeerEntries for the section. 4. User ID 1 exists and can be assigned as an author in that section.

Result:

1. enforceEditEntryPermissions() succeeds on the original entry state. 2. populateEntryModel() reads authors[]=1 from the request body. 3. setAttributesFromRequest() updates authorIds because canChangeAuthor() is evaluated against the old authorship state. 4. saveElement() persists the change and saveAuthors() rewrites the entry-author relation. 5. Entry 345 now appears authored by user 1.

Impact

This allows low-privileged users to falsify content ownership and alter the authorship of entries without having the dedicated author-management permission. The impact includes corrupted audit trails, misleading notifications, broken approval workflows, and unauthorized reassignment of content responsibility.

1 / 2
Source: GitHub
First published (updated )
Severity
7.1
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:L/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

AssetsController::actionDeleteFolder() only requires the deleteAssets:<volume-uid> permission for the target folder. It never enforces deletePeerAssets:<volume-uid>, even though Assets::deleteFoldersByIds() cascades deletion to every descendant folder and every asset inside, regardless of who uploaded them. A low-privilege user who has been granted folder-management rights on a shared volume can therefore destroy assets uploaded by other users (peer assets), bypassing the per-asset peer-permission check that the sibling actionDeleteAsset endpoint correctly applies.

This is the same bug class that was just fixed in actionMoveFolder as GHSA-3w32-23wj-rxg3 (commit 05c2042, Apr 23 2026); the fix added requireVolumePermissionByFolder('deletePeerAssets', …) and savePeerAssets checks to the move endpoint but did not propagate to the delete-folder endpoint.

Details

src/controllers/AssetsController.php:552-569:

php public function actionDeleteFolder(): Response { $this->requireAcceptsJson(); $folderId = $this->request->getRequiredBodyParam('folderId');

$assets = Craft::$app->getAssets(); $folder = $assets->getFolderById($folderId);

if (!$folder) { throw new BadRequestHttpException('The folder cannot be found'); }

// Check if it's possible to delete objects in the target volume. $this->requireVolumePermissionByFolder('deleteAssets', $folder); // <-- only checks deleteAssets $assets->deleteFoldersByIds($folderId);

return $this->asSuccess(); }

requireVolumePermissionByFolder() (src/controllers/AssetsControllerTrait.php:75-88) only resolves to a single requirePermission('deleteAssets:<vol-uid>') call. The peer-equivalent helper (requirePeerVolumePermissionByAsset) is never invoked because there is no folder-level peer helper that iterates the folder's contents.

Assets::deleteFoldersByIds() (src/services/Assets.php:311-349) then enumerates the folder + every descendant folder, queries every asset under those IDs, and calls Craft::$app->getElements()->deleteElement($asset, true) directly:

php $assetQuery = Asset::find()->folderId($allFolderIds); $elementService = Craft::$app->getElements();

foreach (Db::each($assetQuery) as $asset) { $asset->keepFileOnDelete = !$deleteDir; $elementService->deleteElement($asset, true); }

This bypasses Asset::canDelete() (src/elements/Asset.php:1515-1536):

php public function canDelete(User $user): bool { if ($this->isFolder) { return false; } if (parent::canDelete($user)) { return true; } $volume = $this->getVolume(); if (Assets::isTempUploadFs($volume->getFs())) { return true; }

if ($this->uploaderId !== $user->id) { return $user->can("deletePeerAssets:$volume->uid"); // <-- never reached on cascade delete } return $user->can("deleteAssets:$volume->uid"); }

Compare to actionDeleteAsset (src/controllers/AssetsController.php:579-613), which correctly does:

php $this->requireVolumePermissionByAsset('deleteAssets', $asset); $this->requirePeerVolumePermissionByAsset('deletePeerAssets', $asset);

The fix that landed in 05c2042 for actionMoveFolder (src/controllers/AssetsController.php:733-765) added both savePeerAssets and deletePeerAssets requireVolumePermissionByFolder checks to mirror the per-asset pattern, but the same hardening was not applied to actionDeleteFolder or actionRenameFolder (which also calls deleteFoldersByIds indirectly through later logic).

The asymmetry between the two endpoints demonstrates the missing check.

Impact

- Integrity / availability of other users' assets on any volume where the attacker has deleteAssets but not deletePeerAssets: the attacker can permanently delete peer-owned files (and their parent folder structure) on the underlying filesystem, with no recovery via Craft's UI. - The Craft permission model explicitly distinguishes "delete your own assets" (deleteAssets) from "delete other users' assets" (deletePeerAssets) precisely so administrators can grant the former without the latter on shared volumes — this finding renders that distinction unenforceable for any user given folder-delete rights. - No information disclosure or remote code execution; impact is bounded to the affected volume's contents. - Does not require any non-default configuration: the affected endpoint is enabled by default and only requires that an administrator has split deleteAssets from deletePeerAssets (the documented, supported permission model).

1 / 2
Source: GitHub
First published (updated )
Severity
7.1
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

There is a mass-assignment flaw in the bulk-duplicate element action. Alice, holding only the permission to duplicate an entry she owns, submits an arbitrary id through the newAttributes request parameter. The duplication routine overrides its own id = null reset with that value and writes Alice’s attributes into Bob’s existing entry row.

Details

ElementsController::beforeAction() (src/controllers/ElementsController.php:119-124) pulls the request body into $this->attributes and rejects requests that ship an id or canonicalId key at the top level:

php $this->attributes = $this->request->getBodyParams();

// No funny business if (isset($this->attributes['id']) || isset($this->attributes['canonicalId'])) { throw new BadRequestHttpException('Changing an element’s ID is not allowed.'); }

The check inspects only the top-level payload. actionBulkDuplicate() (src/controllers/ElementsController.php:1708-1749) reads a separate newAttributes array and passes it straight through to the service layer:

php $elementInfo = $this->request->getRequiredBodyParam('elements'); $newAttributes = $this->request->getRequiredBodyParam('newAttributes'); ... $safeNewAttributes = Collection::make($newAttributes) ->only($element->safeAttributes()) ->all(); ... $newElement = $elementsService->duplicateElement( $element, $safeNewAttributes + $element::baseBulkDuplicateAttributes(), false, checkAuthorization: true, );

Elements::duplicateElement() (src/services/Elements.php:1814-1840) clones the source element, sets id to null, and then hands the attacker's array to Craft::configure():

php $mainClone = clone $element; $mainClone->id = null; $mainClone->uid = StringHelper::UUID(); ... Craft::configure($mainClone, ArrayHelper::merge( $newAttributes, $siteAttributes[$mainClone->siteId] ?? [], ));

Craft::configure() overwrites the reset id with any numeric value inside $newAttributes. Yii's saveElement() then performs an UPDATE against the row with that primary key instead of an INSERT. Alice's title, slug, authorId, postDate, and UID land on Bob’s entry.

safeAttributes() on Entry includes id because the base element model exposes it, so the Collection::only() filter does not strip it.

Impact

A low-privileged author overwrites any other element (entries, categories, users that share the Entry element table inheritance) by predicting or enumerating element IDs. Content integrity on the entire install breaks. The attack requires only the ability to duplicate one entry Alice already owns.

1 / 2
Source: GitHub
First published (updated )
Severity
7.1
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N

Craft CMS versions >= 5.0.0-RC1 before 5.10.6 and >= 4.0.0-RC1 before 4.18.2 interpolate environment variables and secrets (via ${ENVVAR} strings in the elementId parameter) into Twig templates before rendering, even when the Twig sandbox is enabled. An authenticated attacker with control panel access can render a malicious sandboxed Twig template and, using a blind error-based technique across many requests, incrementally leak arbitrary environment variables and secrets. These can be abused to forge sessions (via CRAFTSECURITYKEY), escalate privileges, and steal database, SMTP, API, or blob storage credentials. Fixed in 5.10.6 and 4.18.2.

First published (updated )
Severity
7.1
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N

Craft CMS before 5.10.5 fails to persist updated credential counters after WebAuthn assertion validation in the passkey login endpoint. Attackers can replay captured login request bodies containing requestOptions and response to create additional authenticated sessions for victim accounts.

First published (updated )
Severity
7.1
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N

Craft CMS versions before 5.10.8 contain an authentication bypass vulnerability in the elements/save action that allows authenticated users to change passwords without verification. Attackers with edit users permission can reset any user's password including administrators by exploiting the unprotected newPassword field in the User element save flow.

First published (updated )
Severity
6.9
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N

Craft CMS contains an external control of assumed-immutable web parameter vulnerability. This vulnerability could allow an unauthenticated client to introduce arbitrary values, such as PHP code, to a known local file location on the server. This vulnerability could be chained with CVE-2024-58136 as represented by CVE-2025-32432.

1 / 3
Source: CISA
First published (updated )
Severity
6.9
Path Traversal
AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

Craft CMS versions >= 5.0.0-RC1 before 5.10.6 and >= 4.0.0-RC1 before 4.18.2 contain a theoretical path traversal weakness in the ensurePathIsContained function of the Local file system class. The order of operations validates the path before normalization, so normalization could invalidate prior validation assumptions (a desanitization-style issue) and potentially resolve to files outside the intended volume directory. The vendor notes the issue is not directly exploitable and no exploitable scenario has been discovered; the fix is recommended for hardening.

First published (updated )
Severity
6.9
SSRF
AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N

Craft CMS versions >= 5.0.0-RC1 before 5.10.6 and >= 4.0.0-RC1 before 4.18.2 contain a server-side request forgery vulnerability in the GraphQL save<Volume>Asset mutation, which fetches an attacker-supplied URL server-side. The anti-SSRF validation is incomplete: validateIp() does not cover CGNAT (100.64.0.0/10) or NAT64 (64:ff9b::/96) ranges, and the only IP check runs after the request has already been issued. An attacker holding a GraphQL token scoped only to asset-creation permissions can disclose internal HTTP content from CGNAT/NAT64 targets, force outbound GET requests to internal hosts (including RFC1918, loopback, and metadata endpoints), and enumerate internal services.

First published (updated )
Severity
6
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Summary

The EntriesController::actionMoveToSection() endpoint checks only whether the current user can view the destination section, but it does not require permission to save entries into that section. A low-privileged authenticated control-panel user who can move an entry out of its current section can therefore move that entry into a different section where they have read access but no write access.

Details

The vulnerable route is implemented in EntriesController.php:465:

The destination check is only viewEntries:$section->uid . The source-entry gate is Entry::canMove(), which verifies whether the user can move the existing entry based on the source section:

This closes the exploit chain:

1. External source: authenticated CP request to entries/move-to-section. 2. Missing authorization check: destination section requires only viewEntries, not saveEntries. 3. Privileged sink: moveEntryToSection() rewrites sectionId and saves the entry into the unauthorized section.

Preconditions derived from the code:

1. The attacker is authenticated to the control panel. 2. Entry 345 is movable by the attacker from its current section. 3. The attacker can satisfy viewEntries on destination section 12. 4. The attacker does not have saveEntries:DESTINATIONUID, which is the missing check that makes the bypass possible.

Result:

1. The controller accepts the request because viewEntries:$section->uid passes. 2. Each source entry passes canMove() based on source-section permissions. 3. moveEntryToSection() updates the entry’s sectionId and saves it. 4. The entry is now located in a section where the attacker did not have write permission.

Impact

This breaks the intended section-level authorization model. A user with limited content permissions can inject or relocate content into a protected section, interfering with editorial boundaries, approval workflows, section-specific business logic, and content ownership expectations.

1 / 2
Source: GitHub
First published (updated )
Severity
6
Infoleak
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Craft CMS is a content management system (CMS). In versions starting from 4.0.0-RC1 and prior to 4.18.0, and 5.0.0-RC1 and above, prior to 5.10.0, the dataUrl() Twig function is included in Craft’s Twig sandbox allowlist, allowing any control panel user granted the utility:system-messages permission to embed a file-reading payload into system email templates. When those emails are sent, the server reads the target file and returns its contents as a base64-encoded data URL embedded in the email body. The .env file, which typically contains the database password, CRAFTSECURITYKEY, and third-party API keys, passes all of Craft’s existing dataUrl() protection checks and is fully exfiltrated. Obtaining CRAFTSECURITYKEY enables an attacker to forge session tokens and escalate to full admin account takeover. This issue has been fixed in versions 4.18.0 and 5.10.0.

1 / 2
Source: NVD
First published (updated )
Severity
5.9
XSS, CSRF
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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Stored XSS via Structure entry title in table view

Summary

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.

Preconditions

- Attacker has createEntries + saveEntries on a Structure-type section (Author-style CP account; no admin permission required). - Victim has saveEntries on the same section. Craft only renders drag handles when structureEditable is true, which requires this permission, so any admin qualifies. - Section must be type Structure. Channel and Single sections are unaffected. - Poisoned entry must have no children at the time of the drag (fires on the 0-to-1 descendant transition). - For the email-change account-takeover path, the victim must currently have an elevated session. The stored XSS itself does not require elevation.

Root cause

ElementTableSorter.js lines 643-652:

js 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().

Steps to reproduce

Plant (attacker - Author account):

bash python3 poc.py --url http://target --cp admin \ --user author@example.com --pass secret --section mySection

Trigger (victim - any control panel user with saveEntries, e.g. admin):

1. Open the section index in Table view. 2. Drag the "Drag me" entry and drop it as the first child of the poisoned entry.

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):

html "><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.

Impact

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.

Mitigating factors

- Requires an existing control panel account (Author role minimum). - Victim must perform a drag operation, not just visit the page. - The demonstrated email-change takeover requires the victim’s session to be elevated at trigger time.

Resources

https://github.com/craftcms/cms/commit/162321e899cc97517fb6f5a02b5528f549d0c6cc

1 / 2
Source: GitHub
First published (updated )
Severity
5.5
SSRF
CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N/E:P/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Craft CMS is a content management system (CMS). In versions on the 4.x branch through 4.17.8 and the 5.x branch through 5.9.14, the resource-js endpoint in Craft CMS allows unauthenticated requests to proxy remote JavaScript resources. When trustedHosts is not explicitly restricted (default configuration), the application trusts the client-supplied Host header. This allows an attacker to control the derived baseUrl, which is used in prefix validation inside actionResourceJs(). By supplying a malicious Host header, the attacker can make the server issue arbitrary HTTP requests, leading to Server-Side Request Forgery (SSRF). Versions 4.17.9 and 5.9.15 patch the issue.

First published (updated )
Severity
5.3
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Craft CMS is a content management system (CMS). In versions 5.6.0 through 5.9.14, the actionSavePermissions() endpoint allows a user with only viewUsers permission to remove arbitrary users from all user groups. While saveUserGroups() enforces per-group authorization for additions, it performs no equivalent authorization check for removals, so submitting an empty groups value removes all existing group memberships. Version 5.9.15 contains a patch.

First published (updated )
Severity
5.3
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Craft CMS is a content management system (CMS). Versions 5.0.0-RC1 through 5.9.20, and 4.0.0-RC1 through 4.17.13 contain an authorization issue in the AssetsController::actionReplaceFile that can delete a source asset without source delete permission by supplying both assetId and sourceAssetId. AssetsController::actionReplaceFile() supports replacing a target asset file using another existing asset as the source. The action loads: assetId -> $assetToReplace and sourceAssetId -> $sourceAsset, then enforces replace permissions using ($assetToReplace ?: $sourceAsset). When both IDs are provided, this expression resolves to the target asset so no permission check is performed against the source asset volume. When both assets are present, Craft copies the source file into the target and then deletes the source asset. There is no deletion check for for the source asset. An authenticated user who can replace files in one volume can delete assets in another volume where they do not have delete permission, as long as they can obtain a sourceAssetId, leading to broken content references and data loss. This issue has been fixed in versions 4.17.14 and 5.9.21.

1 / 2
Source: MITRE
First published (updated )
Severity
5.3
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

A vulnerability was detected in Craft CMS up to 4.18.0.1. Affected is the function actionReorderSets of the file src/controllers/GlobalsController.php of the component reorder-sets Endpoint. The manipulation results in authorization bypass. The attack can be executed remotely. Upgrading to version 4.18.1 is able to address this issue. The patch is identified as 9bd05c91e6a7e6da5e949ec41a31c220c059aa04. The affected component should be upgraded.

1 / 2
Source: NVD
First published (updated )
Severity
5.3
AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N/E:X/RL:O/RC:C

A flaw has been found in Craft CMS up to 4.18.0.1. Affected by this vulnerability is the function actionGetNewUsersData of the file src/controllers/ChartsController.php of the component Charts Endpoint. This manipulation of the argument userGroupId causes improper authorization. The attack is possible to be carried out remotely. Upgrading to version 4.18.1 addresses this issue. Patch name: 9ee53efc1314e6aba32771c66a13e072a246f4ce. It is suggested to upgrade the affected component.

First published (updated )
Severity
4.9
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N/E:U/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X

Craft CMS is a content management system (CMS). Versions 5.0.0-RC1 and above, prior to 5.9.21 and versions 4.0.0-RC1 and above prior to 4.17.14 contain an authorization issue where a forced folder move can delete a conflicting destination folder without destination delete permission. Function craft\\controllers\\AssetsController::actionMoveFolder() supports moving an asset folder into a destination parent folder. If a folder with the same name already exists at the destination, the action can be called with force=true to overwrite the destination. This issue has been resolved in versions 5.9.21 and 4.17.14.

1 / 2
Source: MITRE
First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203