GitHub Security Advisory: GHSA-wwg8-6ffr-h4q2
Package: admidio/admidio (COMPOSER)
Severity: MODERATE (CVSS 5.7)
CWE: CWE-352: Cross-Site Request Forgery (CSRF)
Vulnerable versions: >= 5.0.0, <= 5.0.6
Patched in: 5.0.7

Summary: Admidio is Missing CSRF Validation on Role Delete, Activate, and Deactivate Actions

Details:
## Summary

The `delete`, `activate`, and `deactivate` modes in `modules/groups-roles/groups_roles.php` perform destructive state changes on organizational roles but never validate an anti-CSRF token. The client-side UI passes a CSRF token to `callUrlHideElement()`, which includes it in the POST body, but the server-side handlers ignore `$_POST["adm_csrf_token"]` entirely for these three modes. An attacker who can discover a role UUID (visible in the public `cards` view when the module is publicly accessible) can embed a forged POST form on any external page and trick any user with the `rol_assign_roles` right into deleting or toggling roles for the organization. Role deletion is permanent and cascades to all memberships, event associations, and rights data.

## Details

### CSRF Token Is Sent but Never Validated

File: `D:/bugcrowd/admidio/repo/modules/groups-roles/groups_roles.php`, lines 150-173

The `save` mode (lines 143-148) is CSRF-protected via `RolesService::save()` which calls `getFormObject($_POST["adm_csrf_token"])->validate()`. The `delete`, `activate`, and `deactivate` modes receive no equivalent protection:

```php
case 'delete':
    // delete role from database
    $role = new Role($gDb);
    $role->readDataByUuid($getRoleUUID);
    if ($role->delete()) {
        echo json_encode(array('status' => 'success'));
    }
    break;

case 'activate':
    // set role active
    $role = new Role($gDb);
    $role->readDataByUuid($getRoleUUID);
    $role->activate();
    echo 'done';
    break;

case 'deactivate':
    // set role inactive
    $role = new Role($gDb);
    $role->readDataByUuid($getRoleUUID);
    $role->deactivate();
    echo 'done';
    break;
```

The only input validated is `$getRoleUUID` at line 41, checked as a `'uuid'` type. This prevents SQL injection but provides no CSRF protection.

### Client-Side UI Passes Token; Server Ignores It

File: `D:/bugcrowd/admidio/repo/system/js/common_functions.js`, lines 101-129

The presenter embeds the CSRF token into the JavaScript `callUrlHideElement()` call (GroupsRolesPresenter.php line 131). The function sends it in an AJAX POST body:

```javascript
function callUrlHideElement(elementId, url, csrfToken, callback) {
    $.post(url, {
        "adm_csrf_token": csrfToken,  // sent in POST body
        "uuid": elementId
    }, function(data) { ... });
}
```

The server-side handler reads `mode` from `$_GET` but never reads or validates `$_POST["adm_csrf_token"]` for `delete`, `activate`, or `deactivate`. An attacker omits the token field entirely; the server does not check for its presence.

### Who Can Be the CSRF Victim

File: `D:/bugcrowd/admidio/repo/modules/groups-roles/groups_roles.php`, lines 49-54

```php
if ($getMode !== 'cards') {
    // only users with the special right are allowed to manage roles
    if (!$gCurrentUser->isAdministratorRoles()) {
        throw new Exception('SYS_NO_RIGHTS');
    }
}
```

`isAdministratorRoles()` maps to `checkRolesRight('rol_assign_roles')