{"id":"CVE-2026-34384","aliases":["GHSA-ph84-r98x-2j22"],"url":"https://o3.security/vulnerability/CVE-2026-34384","summary":"Admidio: Missing CSRF Protection on Registration Approval Actions","details":"## Summary\n\nThe create_user, assign_member, and assign_user action modes in modules/registration.php approve pending user registrations via GET request without validating a CSRF token. Unlike the delete_user 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 rol_approve_users right into visiting a crafted URL that automatically approves the registration. This bypasses the manual registration approval workflow entirely.\n\n## Details\n\n### CSRF Protection Is Present for delete_user but Absent for Approval Modes\n\nFile: modules/registration.php, lines 90-128\n\nThe delete_user mode validates the CSRF token (line 99), but the three approval modes do not:\n\n```php\n// assign_member and assign_user: no CSRF check\n} elseif (in_array($getMode, array('assign_member', 'assign_user'))) {\n    $registrationService = new RegistrationService($gDb, $getUserUUID);\n    $message = $registrationService->assignRegistration($getUserUUIDAssigned, $getMode === 'assign_member');\n    $gMessage->setForwardUrl($message['forwardUrl']);\n    $gMessage->show($message['message']);\n\n// create_user: no CSRF check\n} elseif ($getMode === 'create_user') {\n    $registrationUser->acceptRegistration();\n    if ($gCurrentUser->isAdministratorRoles()) {\n        admRedirect(SecurityUtils::encodeUrl(ADMIDIO_URL . FOLDER_MODULES.'/profile/roles.php',\n            array('accept_registration' => true, 'user_uuid' => $getUserUUID)));\n    }\n\n// delete_user: CSRF IS validated\n} elseif ($getMode === 'delete_user') {\n    SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']); // <-- protected\n    $registrationUser->delete();\n}\n```\n\nThe three approval modes read both UUIDs exclusively from $_GET (lines 41-43):\n\nThe approve action modes accept $_GET parameters `user_uuid` and `user_uuid_assigned` 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.\n\n### User UUID Is Known to the Attacker from Registration Email\n\nFile: `D:/bugcrowd/admidio/repo/src/Infrastructure/Service/RegistrationService.php`, lines 154-157\n\nWhen a user submits a registration, Admidio sends a confirmation email containing a URL of the form:\n\n```\nhttps://TARGET/adm_program/modules/registration.php?id=VALIDATION_ID&user_uuid=REGISTRANT_UUID\n```\n\nThe `user_uuid` in this URL is the registrant's own UUID. The attacker has this UUID because they received the confirmation email for their own registration.\n\n### isAdministratorRegistration() Is a Delegated Right\n\nFile: `D:/bugcrowd/admidio/repo/src/Users/Entity/User.php`, lines 1603-1606\n\n```php\npublic function isAdministratorRegistration(): bool\n{\n    return $this->checkRolesRight('rol_approve_users');\n}\n```\n\nThe `rol_approve_users` 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.\n\n## PoC\n\n**Scenario: Attacker bypasses manual registration approval**\n\nPrerequisites: (1) Manual registration approval is enabled. (2) The attacker submits a registration form and receives a confirmation email with their `user_uuid`. (3) After clicking the confirmation link, their registration enters the pending queue.\n\n**Step 1: Attacker extracts their own user_uuid from the registration email**\n\nThe confirmation email contains a link of the form:\n\n```\nhttps://TARGET/adm_program/modules/registration.php?id=VALIDATION_ID&user_uuid=ATTACKER_UUID\n```\n\nThe `ATTACKER_UUID` is visible to the attacker from their own email.\n\n**Step 2: CSRF auto-approval via image tag**\n\nThe attacker hosts a page that the victim (admin with `rol_approve_users` right) visits:\n\n```html\n<img src=\"https://TARGET/adm_program/modules/registration.php?mode=create_user&user_uuid=ATTACKER_UUID\" width=\"1\" height=\"1\">\n```\n\nWhen the victim loads this page, Admidio silently accepts the attacker registration and assigns default organization roles. No confirmation or token is required.\n\n**Step 3: Force-assign registration to an existing account (account takeover)**\n\nIf 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:\n\n```html\n<img src=\"https://TARGET/adm_program/modules/registration.php?mode=assign_user&user_uuid=ATTACKER_REG_UUID&user_uuid_assigned=EXISTING_USER_UUID\" width=\"1\" height=\"1\">\n```\n\nThis merges the pending registration into the existing account, replacing that account login credentials with the attacker credentials.\n\n## Impact\n\n- **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.\n- **Account Takeover via assign_user CSRF:** If the attacker knows any member UUID (visible in profile page URLs), the `assign_user` 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.\n- **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.\n- **Delegated Right:** The required victim right (`rol_approve_users`) is a common delegation target in organizations with membership approval workflows.\n\n## Recommended Fix\n\nAdd `SecurityUtils::validateCsrfToken($_POST[\"adm_csrf_token\"])` at the beginning of each approval action, consistent with how `delete_user` is already protected in the same file.\n\n```php\n// File: modules/registration.php\n\n} elseif (in_array($getMode, array('assign_member', 'assign_user'))) {\n    // ADD: validate CSRF token\n    SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']);\n    $registrationService = new RegistrationService($gDb, $getUserUUID);\n    $message = $registrationService->assignRegistration($getUserUUIDAssigned, $getMode === 'assign_member');\n    ...\n\n} elseif ($getMode === 'create_user') {\n    // ADD: validate CSRF token\n    SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']);\n    $registrationUser->acceptRegistration();\n    ...\n\n} elseif ($getMode === 'delete_user') {\n    SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']); // already protected\n    $registrationUser->delete();\n}\n```\n\nAdditionally, convert the approval action URLs from GET-based links to POST-form buttons (with the CSRF token in a hidden field). The existing `delete_user` button uses `callUrlHideElement()` which already sends the token in the POST body -- use the same pattern for approval buttons.","published":"2026-03-31T20:34:37.789Z","modified":"2026-08-12T03:51:22.929456822Z","cvss":{"score":4.5,"severity":"MEDIUM","vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:N/I:H/A:N"},"epss":null,"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"Packagist","name":"admidio/admidio","fixedVersion":"5.0.8"}],"fix":{"url":"https://github.com/Admidio/admidio/commit/707171c188b3e8f36007fc3f2bccbfac896ed019","label":"Admidio/admidio@707171c"},"references":[{"type":"ADVISORY","url":"https://github.com/Admidio/admidio/security/advisories/GHSA-ph84-r98x-2j22"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/34xxx/CVE-2026-34384.json"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-34384"},{"type":"FIX","url":"https://github.com/Admidio/admidio/commit/707171c188b3e8f36007fc3f2bccbfac896ed019"},{"type":"PACKAGE","url":"https://github.com/Admidio/admidio"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-08-12T03:51:22.929456822Z"}}