GitHub Security Advisory: GHSA-h8gr-qwr6-m9gx
Package: admidio/admidio (COMPOSER)
Severity: MODERATE (CVSS 5.7)
CWE: CWE-352: Cross-Site Request Forgery (CSRF)
Vulnerable versions: <= 5.0.6
Patched in: 5.0.7

Summary: Admidio is Missing CSRF Protection on Role Membership Date Changes

Details:
## 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-42

The CSRF guard covers only two of the three mutative modes:

```php
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:

```php
} 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-169

### The Form Does Generate a CSRF Token (Not Validated)

File: `D:/bugcrowd/admidio/repo/modules/profile/roles_functions.php`, lines 218-241

The 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-121

The `allowedToAssignMembers()` check grants write access to:
- Any user who is `isAdministratorRoles()` (role administrators), or
- 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`

Role leaders are not system administrators. They are regular members w