OSV 1.4.0 · github-reviewed · 修改于 2026-07-03 02:45
发布时间
2026-07-03 02:45
GitHub 审查时间
2026-07-03 02:45
NVD 发布时间
2026-07-02 08:16
源文件
advisories/github-reviewed/2026/07/GHSA-qq2c-2q8j-jh27/GHSA-qq2c-2q8j-jh27.json
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.
The control flow begins in EntriesController.php:249. actionSaveEntry() loads the entry and enforces edit permissions before calling _populateEntryModel():
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:
$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:
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:
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:
entries/save-entry with attacker-controlled authors[].Preconditions derived from the source:
345.345, or otherwise satisfies canChangeAuthor() through the old author set.viewPeerEntries for the section.1 exists and can be assigned as an author in that section.Result:
enforceEditEntryPermissions() succeeds on the original entry state._populateEntryModel() reads authors[]=1 from the request body.setAttributesFromRequest() updates authorIds because canChangeAuthor() is evaluated against the old authorship state.saveElement() persists the change and _saveAuthors() rewrites the entry-author relation.345 now appears authored by user 1.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.