PT-2026-55413 · Packagist · Craftcms/Cms
Publicado
2026-07-02
·
Atualizado
2026-07-02
CVSS v4.0
7.6
Alta
| Vetor | AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N |
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(array filter([
'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) ||
in array($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:
- External source: authenticated request to
entries/save-entrywith attacker-controlledauthors[]. - Trust boundary failure: authorization is checked on the pre-mutation entry state, not on the post-mutation author assignment.
- Privileged sink: the author relationship is rewritten in persistent storage.
Preconditions derived from the source:
- The attacker is authenticated and can edit entry
345. - The attacker is among the existing authors of entry
345, or otherwise satisfiescanChangeAuthor()through the old author set. - The attacker has
viewPeerEntriesfor the section. - User ID
1exists and can be assigned as an author in that section.
Result:
enforceEditEntryPermissions()succeeds on the original entry state.populateEntryModel()readsauthors[]=1from the request body.setAttributesFromRequest()updatesauthorIdsbecausecanChangeAuthor()is evaluated against the old authorship state.saveElement()persists the change andsaveAuthors()rewrites the entry-author relation.- Entry
345now appears authored by user1.
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.
Correção
Improper Authorization
Encontrou algum problema na descrição? Tem algo a acrescentar? Fique à vontade para nos escrever 👾
Enumeração de Fraquezas
Identificadores relacionados
Produtos afetados
Craftcms/Cms