CVE-2026-34384: Admidio: Missing CSRF Protection on Registration Approval Actions
Summary
The createuser, assignmember, and assignuser action modes in modules/registration.php approve pending user registrations via GET request without validating a CSRF token. Unlike the deleteuser mode in the same file (which correctly validates the token), these three approval actions read their parameters from $GET and perform irreversible state changes without any protection. An attacker who has submitted a pending registration can extract their own user UUID from the registration confirmation email URL, then trick any user with the rolapproveusers right into visiting a crafted URL that automatically approves the registration. This bypasses the manual registration approval workflow entirely.
Details
CSRF Protection Is Present for deleteuser but Absent for Approval Modes
File: modules/registration.php, lines 90-128
The deleteuser mode validates the CSRF token (line 99), but the three approval modes do not:
php // assignmember and assignuser: no CSRF check } elseif (inarray($getMode, array('assignmember', 'assignuser'))) { $registrationService = new RegistrationService($gDb, $getUserUUID); $message = $registrationService->assignRegistration($getUserUUIDAssigned, $getMode === 'assignmember'); $gMessage->setForwardUrl($message['forwardUrl']); $gMessage->show($message['message']);
// createuser: no CSRF check } elseif ($getMode === 'createuser') { $registrationUser->acceptRegistration(); if ($gCurrentUser->isAdministratorRoles()) { admRedirect(SecurityUtils::encodeUrl(ADMIDIOURL . FOLDERMODULES.'/profile/roles.php', array('acceptregistration' => true, 'useruuid' => $getUserUUID))); }
// deleteuser: CSRF IS validated } elseif ($getMode === 'deleteuser') { SecurityUtils::validateCsrfToken($POST['admcsrftoken']); // <-- protected $registrationUser->delete(); }
The three approval modes read both UUIDs exclusively from $GET (lines 41-43):
The approve action modes accept $GET parameters useruuid and useruuidassigned without any POST body or CSRF token. Both parameters pass through admFuncVariableIsValid() with uuid type validation, which prevents SQL injection but provides no CSRF protection.
User UUID Is Known to the Attacker from Registration Email
File: D:/bugcrowd/admidio/repo/src/Infrastructure/Service/RegistrationService.php, lines 154-157
When a user submits a registration, Admidio sends a confirmation email containing a URL of the form:
https://TARGET/admprogram/modules/registration.php?id=VALIDATIONID&useruuid=REGISTRANTUUID
The useruuid in this URL is the registrant's own UUID. The attacker has this UUID because they received the confirmation email for their own registration.
isAdministratorRegistration() Is a Delegated Right
File: D:/bugcrowd/admidio/repo/src/Users/Entity/User.php, lines 1603-1606
php public function isAdministratorRegistration(): bool { return $this->checkRolesRight('rolapproveusers'); }
The rolapproveusers right is a delegated organizational privilege, not full system administrator access. Any member designated to review registrations -- for example, a membership secretary or club administrator -- is a valid CSRF victim.
PoC
Scenario: Attacker bypasses manual registration approval
Prerequisites: (1) Manual registration approval is enabled. (2) The attacker submits a registration form and receives a confirmation email with their useruuid. (3) After clicking the confirmation link, their registration enters the pending queue.
Step 1: Attacker extracts their own useruuid from the registration email
The confirmation email contains a link of the form:
https://TARGET/admprogram/modules/registration.php?id=VALIDATIONID&useruuid=ATTACKERUUID
The ATTACKERUUID is visible to the attacker from their own email.
Step 2: CSRF auto-approval via image tag
The attacker hosts a page that the victim (admin with rolapproveusers right) visits:
html <img src="https://TARGET/admprogram/modules/registration.php?mode=createuser&useruuid=ATTACKERUUID" width="1" height="1">
When the victim loads this page, Admidio silently accepts the attacker registration and assigns default organization roles. No confirmation or token is required.
Step 3: Force-assign registration to an existing account (account takeover)
If the attacker knows the UUID of an existing member (obtainable from profile page URLs when the user list is visible) and has a pending registration:
html <img src="https://TARGET/admprogram/modules/registration.php?mode=assignuser&useruuid=ATTACKERREGUUID&useruuidassigned=EXISTINGUSERUUID" width="1" height="1">
This merges the pending registration into the existing account, replacing that account login credentials with the attacker credentials.
Impact
- Manual Approval Bypass: An attacker with a pending registration can force auto-approval without waiting for an administrator to manually review it. This grants them organization membership, including access to events, documents, mailing lists, and other role-restricted features. - Account Takeover via assignuser CSRF: If the attacker knows any member UUID (visible in profile page URLs), the assignuser mode merges the attacker registration into that member account, replacing the existing member login with the attacker credentials. This is a full account takeover requiring only that the victim admin visit a crafted URL. - Low Attack Complexity: The attacker only needs their own registration email to get their UUID. The CSRF payload is a plain GET request via an image tag -- no JavaScript required. - Delegated Right: The required victim right (rolapproveusers) is a common delegation target in organizations with membership approval workflows.
Recommended Fix
Add SecurityUtils::validateCsrfToken($POST["admcsrftoken"]) at the beginning of each approval action, consistent with how deleteuser is already protected in the same file.
php // File: modules/registration.php
} elseif (inarray($getMode, array('assignmember', 'assignuser'))) { // ADD: validate CSRF token SecurityUtils::validateCsrfToken($POST['admcsrftoken']); $registrationService = new RegistrationService($gDb, $getUserUUID); $message = $registrationService->assignRegistration($getUserUUIDAssigned, $getMode === 'assignmember'); ...
} elseif ($getMode === 'createuser') { // ADD: validate CSRF token SecurityUtils::validateCsrfToken($POST['admcsrftoken']); $registrationUser->acceptRegistration(); ...
} elseif ($getMode === 'deleteuser') { SecurityUtils::validateCsrfToken($POST['admcsrftoken']); // already protected $registrationUser->delete(); }
Additionally, convert the approval action URLs from GET-based links to POST-form buttons (with the CSRF token in a hidden field). The existing deleteuser button uses callUrlHideElement() which already sends the token in the POST body -- use the same pattern for approval buttons.
Other sources
Admidio is an open-source user management solution. Prior to version 5.0.8, the createuser, assignmember, and assignuser action modes in modules/registration.php approve pending user registrations via GET request without validating a CSRF token. Unlike the deleteuser mode in the same file (which correctly validates the token), these three approval actions read their parameters from $GET and perform irreversible state changes without any protection. An attacker who has submitted a pending registration can extract their own user UUID from the registration confirmation email URL, then trick any user with the rolapproveusers right into visiting a crafted URL that automatically approves the registration. This bypasses the manual registration approval workflow entirely. This issue has been patched in version 5.0.8.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
composer/admidio/admidioto a version that resolves this vulnerability.Fixed in 5.0.8 - Upgrade
Upgrade to a fixed release to a version that resolves this vulnerability.
Fixed in 5.0.8 - Configuration
In modules/registration.php, add SecurityUtils::validateCsrfToken($_POST["adm_csrf_token"]) to the action handlers for the approval modes create_user, assign_member, and assign_user (these modes currently approve via GET and lack CSRF protection; delete_user already validates the CSRF token and should serve as the pattern).
modules/registration.php CSRF token validation for approval modes (assign_member, assign_user, create_user) = Add SecurityUtils::validateCsrfToken($_POST["adm_csrf_token"]) at the beginning of each approval action - Configuration
Replace GET-based approval links for modes create_user, assign_member, and assign_user with POST-form buttons so that adm_csrf_token is sent in the POST body (use the same approach as delete_user which sends the token via callUrlHideElement()).
modules/registration.php Approval action request method (GET->POST) = Convert approval action URLs to POST-form buttons including adm_csrf_token as a hidden field
Event History
Frequently Asked Questions
What is the severity of CVE-2026-34384?
The severity of CVE-2026-34384 is rated as medium due to the potential for unauthorized user approvals.
How do I fix CVE-2026-34384?
To fix CVE-2026-34384, upgrade to Admidio version 5.0.8 or later, which implements CSRF protection.
Which versions of Admidio are affected by CVE-2026-34384?
All versions of Admidio prior to version 5.0.8 are affected by CVE-2026-34384.
What actions are vulnerable in CVE-2026-34384?
The create_user, assign_member, and assign_user action modes in modules/registration.php are vulnerable in CVE-2026-34384.
Does CVE-2026-34384 affect user security?
Yes, CVE-2026-34384 poses a risk to user security by allowing unauthorized registration approvals.