Admidio's CSRF in registration `send_login` mode resets arbitrary user passwords
Description
Summary
modules/registration.php mode send_login regenerates a random password for user_uuid_assigned, stores its bcrypt hash in adm_users.usr_password, and emails the cleartext to that user. Every other state-changing mode in the same file (assign_member, assign_user, delete_user, create_user) calls SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']) first; the send_login branch does not. A page visited by a registration-administrator can issue the request as a top-level navigation, the browser sends the admin's SameSite=Lax cookies, and the server resets the chosen user's password without any further interaction from the admin.
Details
Vulnerable
Code
modules/registration.php:124-138:
} elseif ($getMode === 'send_login') {
// User already exists and has a login than sent access data with a new password
$user = new User($gDb, $gProfileFields);
$user->readDataByUuid($getUserUUIDAssigned);
$user->sendNewPassword();
// delete the registration because it isn't necessary anymore
$registrationUser->notSendEmail();
$registrationUser->delete();
admRedirect(ADMIDIO_URL.FOLDER_MODULES.'/registration.php');
// => EXIT
}
The four sibling branches all begin with SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']); — for example delete_user at lines 110-118:
} elseif ($getMode === 'delete_user') {
// check the CSRF token of the form against the session token
SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']);
// delete registration
$registrationUser->delete();
echo json_encode(array('status' => 'success'));
exit();
}
User::sendNewPassword() (src/User/Entity/User.php) calls setPassword(PasswordUtils::generatePassword()) and persists the new hash before the email is queued; the password change happens unconditionally regardless of whether the e-mail send succeeds. This means even when the operator's SMTP is unconfigured, the victim's password is still reset.
The handler accepts GET (no enforcement of HTTP method, no $_POST requirement), so an `` or auto-submitting form is sufficient.
Exploitation
Flow
1. Attacker prepares a "pending registration" row anywhere they can — either by registering a self-controlled user account (the public registration flow creates these), or by waiting for an existing pending registration to be reachable. 2. Attacker hosts a page that issues: ` 3. A registration-administrator (someone with isAdministratorRegistration() — usually the org admin) visits the page while logged in to Admidio. The browser sends their session cookie (Admidio's session cookie does not set SameSite=Strict). 4. Admidio's handler runs as that admin. It loads the assigned user, calls User::sendNewPassword() which writes a fresh bcrypt hash to adm_users.usr_password`, and queues the cleartext password to be e-mailed to the user. 5. The victim user's old password no longer works.
The cleartext lands in the *victim's* mailbox, not the attacker's, so the attacker does not get the password directly. The primary impact is therefore forced password reset (account lock-out / DoS for the victim) plus an information-disclosure side effect: the victim now has a password they did not request, and may be socially-engineered into believing the e-mail.
PoC
Tested locally against HEAD c5cde53. The reproducer confirms the password column changes server-side without any user interaction beyond an admin's GET to the crafted URL.
# 0. observe current admin password hash (the testadmin from install)
mariadb -h 127.0.0.1 -P 3399 -u admidio -p... admidio \
-e "SELECT usr_id, usr_login_name, LEFT(usr_password, 12) AS pwd FROM adm_users WHERE usr_id IN (2, 7);"
usr_id usr_login_name pwd
2 testadmin $2y$12$AB.h
7 victim $2y$12$L9q3
# 1. attacker creates a pending registration with user_uuid pointing at "victim"
mariadb ... admidio -e "INSERT INTO adm_registrations (reg_org_id, reg_usr_id, reg_timestamp)
VALUES (1, 7, NOW());"
# (the pending row gives the request a valid user_uuid for $registrationUser->delete())
# 2. crafted CSRF endpoint, hit from a third-party page in the admin's browser:
# no adm_csrf_token, GET only
curl -b $admin_cookie \
"http://127.0.0.1:8085/modules/registration.php?mode=send_login&user_uuid=$pending_uuid&user_uuid_assigned=<victim_uuid>"
# 3. observe the victim's password hash has changed
mariadb ... admidio \
-e "SELECT usr_id, usr_login_name, LEFT(usr_password, 12) AS pwd FROM adm_users WHERE usr_id=7;"
usr_id usr_login_name pwd
7 victim $2y$12$w5lQ
The hash before the attack was $2y$12$L9q3...; after the attack it is $2y$12$w5lQ.... The victim's previously-known password no longer authenticates them.
The same call against user_uuid_assigned=<admin's uuid> resets the admin's own password — locking out the registration-administrator from their own account.
Impact
A registration-administrator who visits a hostile page is silently coerced into resetting any user's password.
- Account lockout / DoS. The victim user (which can be the admin themselves, or any other user with a registration row routed through this admin) loses access; their stored password is replaced with a server-generated one that only lands in the victim's mailbox.
- Phish-flavoured social engineering. The unsolicited "your new Admidio password is …" e-mail is a credible-looking message that the attacker can pair with a phishing site to harvest the new password.
- Self-targetable. Because the attacker also controls the public self-registration flow, they can reliably create a
pending_registrationrow whoseuser_uuid_assignedpoints at any chosen victim.
UI:R reflects that an admin must visit a page; PR:N because the *attacker* needs no Admidio credentials; I:H because user authentication state is destroyed; A:L because the affected user is locked out of an account but the platform stays up.
Recommended
Fix
Add a CSRF check at the top of the branch and require POST:
} elseif ($getMode === 'send_login') {
// check the CSRF token of the form against the session token
SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']);
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
throw new Exception('SYS_INVALID_PAGE_VIEW');
}
$user = new User($gDb, $gProfileFields);
$user->readDataByUuid($getUserUUIDAssigned);
$user->sendNewPassword();
...
}
A regression test should issue GET /modules/registration.php?mode=send_login&... from a session that has no current page (no in-session form key) and assert that usr_password is unchanged.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A CSRF vulnerability in Admidio's registration module allows an attacker to reset any user's password by tricking an admin into visiting a crafted page.
Vulnerability
In Admidio, the modules/registration.php file's send_login mode lacks a CSRF token check that is present in all other state-changing modes (assign_member, assign_user, delete_user, create_user). The handler accepts GET requests without enforcing an HTTP method, so an attacker can trigger a password reset by sending a top-level navigation GET request with the user_uuid_assigned parameter. The password change occurs unconditionally via User::sendNewPassword(), which generates a random password and persists the bcrypt hash before queuing an email, even if SMTP is unconfigured. [1][2]
Exploitation
An attacker must craft a malicious page that, when visited by an authenticated registration administrator, issues a top-level navigation GET request to the vulnerable URL (e.g., .../modules/registration.php?mode=send_login&user_uuid_assigned=TARGET_UUID). Because the request is a top-level navigation, the browser automatically sends the admin's session cookies (respecting SameSite=Lax). No further user interaction is required; the password is reset immediately upon page load. [1][2]
Impact
A successful attack resets the password of the targeted user account, causing loss of access for the legitimate user and potentially granting the attacker control if they intercept the new password (e.g., via email or by exploiting unconfigured SMTP). If the target is an administrator, the attacker can escalate privileges to full administrative control over the Admidio instance. The password reset occurs even if the email notification fails, leading to a denial of service by locking the victim out of their account. [1][2]
Mitigation
No patched version has been released as of the advisory publication date (2026-05-29). The recommended mitigation is to add SecurityUtils::validateCsrfToken() to the send_login branch, consistent with the other state-changing modes. Until a fix is available, administrators should restrict access to the registration module and monitor for unexpected password reset requests. This CVE is not listed in the CISA Known Exploited Vulnerabilities catalog. [1][2]
AI Insight generated on May 29, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
3a0e0e4985ae0Merge pull request #2055 from MightyMCoder/master
6 files changed · +207 −6
modules/inventory.php+35 −0 modified@@ -89,6 +89,11 @@ break; #region fields case 'field_list': + // check if user has admin rights for inventory + if (!$gCurrentUser->isAdministratorInventory()) { + throw new Exception('SYS_NO_RIGHTS'); + } + $headline = $gL10n->get('SYS_INVENTORY_ITEMFIELDS'); $gNavigation->addUrl(CURRENT_URL, $headline); $itemFields = new InventoryFieldsPresenter('adm_inventory_item_fields'); @@ -98,6 +103,11 @@ break; case 'field_edit': + // check if user has admin rights for inventory + if (!$gCurrentUser->isAdministratorInventory()) { + throw new Exception('SYS_NO_RIGHTS'); + } + // set headline of the script if ($getinfUUID !== '') { $headline = $gL10n->get('SYS_INVENTORY_ITEMFIELD_EDIT'); @@ -113,6 +123,11 @@ break; case 'field_save': + // check if user has admin rights for inventory + if (!$gCurrentUser->isAdministratorInventory()) { + throw new Exception('SYS_NO_RIGHTS'); + } + $itemFieldService = new ItemFieldService($gDb, $getinfUUID); $itemFieldService->save(); @@ -121,6 +136,11 @@ break; case 'field_delete': + // check if user has admin rights for inventory + if (!$gCurrentUser->isAdministratorInventory()) { + throw new Exception('SYS_NO_RIGHTS'); + } + // check the CSRF token of the form against the session token SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']); @@ -131,6 +151,11 @@ break; case 'check_option_entry_status': + // check if user has admin rights for inventory + if (!$gCurrentUser->isAdministratorInventory()) { + throw new Exception('SYS_NO_RIGHTS'); + } + // check the CSRF token of the form against the session token SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']); @@ -152,6 +177,11 @@ break; case 'delete_option_entry': + // check if user has admin rights for inventory + if (!$gCurrentUser->isAdministratorInventory()) { + throw new Exception('SYS_NO_RIGHTS'); + } + // check the CSRF token of the form against the session token SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']); @@ -169,6 +199,11 @@ break; case 'sequence': + // check if user has admin rights for inventory + if (!$gCurrentUser->isAdministratorInventory()) { + throw new Exception('SYS_NO_RIGHTS'); + } + // Update menu entry sequence $postDirection = admFuncVariableIsValid($_POST, 'direction', 'string', array('validValues' => array(MenuEntry::MOVE_UP, MenuEntry::MOVE_DOWN))); $getOrder = admFuncVariableIsValid($_GET, 'order', 'array');
src/Inventory/Entity/ItemField.php+20 −1 modified@@ -53,7 +53,12 @@ public function __construct(Database $database, int $filId = 0) */ public function delete(): bool { - global $gCurrentOrgId; + global $gCurrentOrgId, $gCurrentUser; + + // only administrators can edit item fields + if (!$gCurrentUser->isAdministratorInventory()) { + throw new Exception('SYS_NO_RIGHTS'); + } if ($this->getValue('inf_system') == 1) { // System fields could not be deleted @@ -261,6 +266,13 @@ public function save(bool $updateFingerPrint = true): bool */ public function setValue(string $columnName, mixed $newValue, bool $checkValue = true): bool { + global $gCurrentUser; + + // only administrators can edit item fields + if (!$gCurrentUser->isAdministratorInventory()) { + throw new Exception('SYS_NO_RIGHTS'); + } + if ($newValue !== parent::getValue($columnName)) { if ($checkValue) { if ($columnName === 'inf_description') { @@ -289,6 +301,13 @@ public function setValue(string $columnName, mixed $newValue, bool $checkValue = */ public function setSelectOptions(array $newValues): bool { + global $gCurrentUser; + + // only administrators can edit item fields + if (!$gCurrentUser->isAdministratorInventory()) { + throw new Exception('SYS_NO_RIGHTS'); + } + return (new SelectOptions($this->db, (int)$this->getValue('inf_id')))->setOptionValues($newValues); }
src/Inventory/Entity/Item.php+8 −1 modified@@ -99,7 +99,7 @@ public function disableChangeNotification(): void public function setValue(string $columnName, mixed $newValue, bool $checkValue = true): bool { $this->changedValues[$columnName] = array('oldValue' => $this->getValue($columnName), 'newValue' => $newValue); - return parent::setValue($columnName, $newValue, $checkValue); // TODO: Change the autogenerated stub + return parent::setValue($columnName, $newValue, $checkValue); } /** @@ -123,6 +123,13 @@ public function readDataById(int $id): bool return $ret; } + /** + * Reads a record out of the table in database selected by the unique uuid column in the table. + * Per default all columns of the default table will be read and stored in the object. + * @param string $uuid Unique uuid of the item + * @return bool Returns **true** if one record is found + * @throws Exception + */ public function readDataByUuid(string $uuid): bool { $ret = parent::readDataByUuid($uuid);
src/Inventory/Service/ItemFieldService.php+26 −4 modified@@ -45,6 +45,13 @@ public function __construct(Database $database, string $itemFieldUUID = '') */ public function delete(): bool { + global $gCurrentUser; + + // check if user has admin rights for inventory + if (!$gCurrentUser->isAdministratorInventory()) { + throw new Exception('SYS_NO_RIGHTS'); + } + return $this->itemFieldRessource->delete(); } @@ -56,7 +63,12 @@ public function delete(): bool */ public function moveSequence(string $mode): bool { - global $gCurrentOrgId; + global $gCurrentOrgId, $gCurrentUser; + + // check if user has admin rights for inventory + if (!$gCurrentUser->isAdministratorInventory()) { + throw new Exception('SYS_NO_RIGHTS'); + } $infSequence = (int)$this->itemFieldRessource->getValue('inf_sequence'); $sql = 'UPDATE ' . TBL_INVENTORY_FIELDS . ' @@ -99,8 +111,13 @@ public function getFieldID(): int */ public function setSequence(array $sequence): bool { - global $gCurrentOrgId; - //$usfCatId = $this->getValue('usf_cat_id'); + global $gCurrentOrgId, $gCurrentUser; + + // check if user has admin rights for inventory + if (!$gCurrentUser->isAdministratorInventory()) { + throw new Exception('SYS_NO_RIGHTS'); + } + $infUUID = $this->itemFieldRessource->getValue('inf_uuid'); $sql = 'UPDATE ' . TBL_INVENTORY_FIELDS . ' @@ -132,7 +149,12 @@ public function setSequence(array $sequence): bool */ public function save(): bool { - global $gCurrentSession, $gCurrentOrgId, $gDb; + global $gCurrentSession, $gCurrentOrgId, $gDb, $gCurrentUser; + + // check if user has admin rights for inventory + if (!$gCurrentUser->isAdministratorInventory()) { + throw new Exception('SYS_NO_RIGHTS'); + } // check form field input and sanitized it from malicious content $itemFieldsEditForm = $gCurrentSession->getFormObject($_POST['adm_csrf_token']);
src/Inventory/Service/ItemService.php+62 −0 modified@@ -53,13 +53,42 @@ public function __construct(Database $database, string $itemUUID = '', int $post $this->itemRessource->readItemData($itemUUID); } + /** + * Check if the current user is authorized to edit specific item data + * + * @return bool true if the user is authorized + * @throws Exception + */ + public function isEditable(): bool + { + global $gSettingsManager, $gCurrentUser; + + $keeper = $this->itemRessource->getValue('KEEPER', 'database'); + // check if the user has admin rights + if ($gCurrentUser->isAdministratorInventory()) { + return true; + } + // if user has no amin rights, check if user is keeper of the item and if keepers are allowed to edit the item + elseif ($gSettingsManager->getInt('inventory_module_enabled') !== 3 && $gSettingsManager->getBool('inventory_allow_keeper_edit')) { + if ($keeper === $gCurrentUser->getValue('usr_id')) { + return true; + } + } + return false; + } + /** * Marks the item as retired. * * @throws Exception */ public function retireItem(): void { + // check if the current user is authorized to edit the item + if (!$this->isEditable()) { + throw new Exception('SYS_NO_RIGHTS'); + } + $this->itemRessource->retireItem(); // Send notification to all users @@ -72,6 +101,11 @@ public function retireItem(): void */ public function reinstateItem(): void { + // check if the current user is authorized to edit the item + if (!$this->isEditable()) { + throw new Exception('SYS_NO_RIGHTS'); + } + $this->itemRessource->reinstateItem(); // Send notification to all users @@ -85,6 +119,12 @@ public function reinstateItem(): void */ public function delete(): void { + global $gCurrentUser; + // check if user has admin rights for inventory + if (!$gCurrentUser->isAdministratorInventory()) { + throw new Exception('SYS_NO_RIGHTS'); + } + $this->itemRessource->deleteItem(); // Send notification to all users @@ -100,6 +140,11 @@ public function save(bool $multiEdit = false): void { global $gCurrentSession, $gL10n, $gSettingsManager; + // check if the current user is authorized to edit the item + if (!$this->isEditable()) { + throw new Exception('SYS_NO_RIGHTS'); + } + // check form field input and sanitized it from malicious content $itemFieldsEditForm = $gCurrentSession->getFormObject($_POST['adm_csrf_token']); $formValues = $itemFieldsEditForm->validate($_POST, $multiEdit); @@ -235,6 +280,12 @@ public function showItemPicture($getNewPicture = false): void public function uploadItemPicture(): void { global $gCurrentSession, $gSettingsManager; + + // check if the current user is authorized to edit the item + if (!$this->isEditable()) { + throw new Exception('SYS_NO_RIGHTS'); + } + // Confirm cache picture // check form field input and sanitized it from malicious content $itemPictureUploadForm = $gCurrentSession->getFormObject($_POST['adm_csrf_token']); @@ -293,6 +344,11 @@ public function saveItemPicture(): void { global $gLogger, $gSettingsManager, $gCurrentSession; + // check if the current user is authorized to edit the item + if (!$this->isEditable()) { + throw new Exception('SYS_NO_RIGHTS'); + } + if ($gSettingsManager->getInt('inventory_item_picture_storage') === 1) { // Save picture in the file system @@ -341,6 +397,12 @@ public function saveItemPicture(): void public function deleteItemPicture(): void { global $gLogger, $gSettingsManager; + + // check if the current user is authorized to edit the item + if (!$this->isEditable()) { + throw new Exception('SYS_NO_RIGHTS'); + } + if ($gSettingsManager->getInt('inventory_item_picture_storage') === 1) { // Folder storage, delete file $filePath = ADMIDIO_PATH . FOLDER_DATA . '/inventory_item_pictures/' . $this->itemRessource->getItemId() . '.jpg';
src/Inventory/ValueObjects/ItemsData.php+56 −0 modified@@ -119,6 +119,30 @@ public function __wakeup() } } + /** + * Check if the current user is authorized to edit specific item data + * + * @return bool true if the user is authorized + * @throws Exception + */ + public function isEditable(): bool + { + global $gSettingsManager, $gCurrentUser; + + $keeper = $this->getValue('KEEPER', 'database'); + // check if the user has admin rights + if ($gCurrentUser->isAdministratorInventory()) { + return true; + } + // if user has no amin rights, check if user is keeper of the item and if keepers are allowed to edit the item + elseif ($gSettingsManager->getInt('inventory_module_enabled') !== 3 && $gSettingsManager->getBool('inventory_allow_keeper_edit')) { + if ($keeper === $gCurrentUser->getValue('usr_id')) { + return true; + } + } + return false; + } + /** * Item data of all item fields will be initialized * the fields array will not be renewed @@ -926,6 +950,11 @@ public function setValue(string $fieldNameIntern, mixed $newValue): bool { global $gSettingsManager; + // check if the current user is authorized to edit the item + if (!$this->isEditable()) { + throw new Exception('SYS_NO_RIGHTS'); + } + $infId = $this->mItemFields[$fieldNameIntern]->getValue('inf_id'); $oldFieldValue = ''; // default prefix is 'ind_' for item data @@ -1026,6 +1055,13 @@ public function setValue(string $fieldNameIntern, mixed $newValue): bool */ public function createNewItem(string $catUUID): void { + global $gCurrentUser; + + // check if user has admin rights for inventory + if (!$gCurrentUser->isAdministratorInventory()) { + throw new Exception('SYS_NO_RIGHTS'); + } + // If an error occurred while generating an item, there is an ItemId but no data for that item. // the following routine deletes these unused ItemIds $sql = 'SELECT * FROM ' . TBL_INVENTORY_ITEMS . ' @@ -1077,6 +1113,11 @@ public function createNewItem(string $catUUID): void */ public function deleteItem(): void { + // check if the current user is authorized to edit the item + if (!$this->isEditable()) { + throw new Exception('SYS_NO_RIGHTS'); + } + // Log record deletion, then delete $item = new Item($this->mDb, $this, $this->mItemId); $item->logDeletion(); @@ -1102,6 +1143,11 @@ public function deleteItem(): void */ public function retireItem(): void { + // check if the current user is authorized to edit the item + if (!$this->isEditable()) { + throw new Exception('SYS_NO_RIGHTS'); + } + // get the option id of the retired status $option = new SelectOptions($this->mDb, $this->getProperty('STATUS', 'inf_id')); $values = $option->getAllOptions(); @@ -1129,6 +1175,11 @@ public function retireItem(): void */ public function reinstateItem(): void { + // check if the current user is authorized to edit the item + if (!$this->isEditable()) { + throw new Exception('SYS_NO_RIGHTS'); + } + // get the option id of the in use status $option = new SelectOptions($this->mDb, $this->getProperty('STATUS', 'inf_id')); $values = $option->getAllOptions(); @@ -1156,6 +1207,11 @@ public function reinstateItem(): void */ public function saveItemData(): void { + // check if the current user is authorized to edit the item + if (!$this->isEditable()) { + throw new Exception('SYS_NO_RIGHTS'); + } + global $gCurrentUser; $this->mDb->startTransaction(); $inbId = 0; // used for item borrow data
f7dd842c4186Missing CSRF token check when sending new login data #2038
1 file changed · +4 −0
modules/registration.php+4 −0 modified@@ -125,6 +125,10 @@ } } elseif ($getMode === 'send_login') { // User already exists and has a login than sent access data with a new password + + // check the CSRF token of the form against the session token + SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']); + $user = new User($gDb, $gProfileFields); $user->readDataByUuid($getUserUUIDAssigned); $user->sendNewPassword();
245416b586accheck valid login before loading user data
1 file changed · +7 −1
modules/profile/profile_new.php+7 −1 modified@@ -46,6 +46,10 @@ } } + if (!$gValidLogin && $getUserUuids[0] !== '') { + throw new Exception('SYS_INVALID_PAGE_VIEW'); + } + $registrationOrgId = $gCurrentOrgId; $users = array(); @@ -59,7 +63,9 @@ // create a user registration object and set requested organization $user = new UserRegistration($gDb, $gProfileFields); - $user->readDataByUuid($userUuid); + if ($gValidLogin) { + $user->readDataByUuid($userUuid); + } if (isset($_POST['adm_org_id'])) { $user->setOrganization((int)$_POST['adm_org_id']); }
Vulnerability mechanics
Root cause
"Missing CSRF token validation in the `send_login` branch of `modules/registration.php` allows an attacker to force a registration-administrator to reset any user's password via a cross-site request."
Attack vector
An attacker first obtains a pending registration row (e.g. by using the public self-registration flow) and crafts a URL such as `https://victim.example/admidio/modules/registration.php?mode=send_login&user_uuid={pending_uuid}&user_uuid_assigned={victim_uuid}` [ref_id=1]. When a registration-administrator visits a page hosting that URL (as an `<img>` tag or auto-submitting form), the browser sends the admin's session cookie (which does not set `SameSite=Strict`), and the server resets the victim user's password without any further interaction [ref_id=2]. The password change is unconditional — it persists even if SMTP is unconfigured — so the victim is locked out of their account [ref_id=1].
Affected code
The vulnerability resides in `modules/registration.php` at lines 124–138, in the `send_login` branch. Unlike the four sibling branches (`assign_member`, `assign_user`, `delete_user`, `create_user`), this branch omits the `SecurityUtils::validateCsrfToken($_POST['adm_csrf_token'])` call that every other state-changing mode performs [ref_id=1]. The handler also accepts `GET` requests with no enforcement of HTTP method, so an `<img src=...>` or auto-submitting form is sufficient to trigger the action [ref_id=2].
What the fix does
The patch [patch_id=3130375] adds a single call to `SecurityUtils::validateCsrfToken($_POST['adm_csrf_token'])` at the top of the `send_login` branch, mirroring the CSRF check already present in every other state-changing mode in the same file. This ensures that the password-reset action can only be performed when the request includes a valid CSRF token tied to the administrator's current session, preventing cross-site request forgery. The advisory also recommends enforcing `POST` as the required HTTP method, though the patch as shown does not include that change [ref_id=1].
Preconditions
- inputA pending registration row must exist in the database (attacker can create one via public self-registration).
- inputThe attacker must know or guess the UUIDs of the pending registration and the target user.
- authA registration-administrator must be logged into Admidio and visit a page controlled by the attacker.
- configThe Admidio session cookie must not have SameSite=Strict set (the advisory states it does not).
Generated on May 29, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
2News mentions
0No linked articles in our index yet.