CVE-2026-41657: Admidio/admidio Admidio Exposes Cross-Organization Member Data Vulnerability

MODERATE
Advisory Identifier: GHSA-g8p8-94f2-28gr
CVSS 4.9
CWE-863
Published
Advisory Summary

CVE-2026-41657 is a MODERATE (CVSS 4.9) admidio exposes cross-organization member data vulnerability (CWE-863: Incorrect Authorization) affecting Admidio/admidio libraries in the Packagist ecosystem. The issue involves admidio exposes cross-organization member data via permission check mismatch in contacts_data.php. Remediation is available by upgrading affected packages to version 5.0.9.

CVE-2026-41657 at a Glance

CVE Identifier CVE-2026-41657
GHSA Identifier GHSA-g8p8-94f2-28gr
Severity MODERATE
CVSS v3.1 Base Score 4.9
CWE Classification CWE-863: Incorrect Authorization
Affected Product Admidio/admidio
Package Ecosystem Packagist
Published Date
Last Updated

Security Impact

CVSS: 4.9

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

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

How to Fix CVE-2026-41657

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.9
Upgrade admidio/admidio to 5.0.9
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-41657, 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.9), 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-863: Incorrect Authorization, where the product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. 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 `contacts_data.php` endpoint uses a weaker permission check (`isAdministratorUsers()`, requiring only `rol_edit_user=true`) than the frontend UI (`contacts.php`) which correctly requires the stronger `isAdministrator()` (requiring `rol_administrator=true`) and the `contacts_show_all` system setting. A user manager who is not a full administrator can directly request `contacts_data.php?mem_show_filter=3` to retrieve all user records across all organizations in the Admidio instance, bypassing multi-tenant organization isolation. ## Details The frontend page `contacts.php` and the backend data endpoint `contacts_data.php` have mismatched authorization checks for the "show all organizations" filter (`mem_show_filter=3`). **Frontend guard** at `modules/contacts/contacts.php:80`: ```php if ($gCurrentUser->isAdministrator() && $gSettingsManager->getBool('contacts_show_all')) { // Only then is filter=3 ("All Organizations") shown in the dropdown $selectBoxValues = array( ... '3' => array('3', $gL10n->get('SYS_ALL_CONTACTS'), $gL10n->get('SYS_ALL_ORGANIZATIONS')) ); } ``` This correctly requires both `isAdministrator()` (`rol_administrator=true`) AND the `contacts_show_all` setting. **Backend check** at `modules/contacts/contacts_data.php:235`: ```php } elseif (($getMembersShowFilter === 3) && $gCurrentUser->isAdministratorUsers()) { $mainSql = $contactsListConfig->getSql( array( 'showAllMembersDatabase' => true, ... ) ); ``` This only requires `isAdministratorUsers()` which checks `rol_edit_user=true` — a weaker permission available to non-admin "user manager" roles. The `contacts_show_all` setting is never checked. **The critical difference between the two methods** (from `src/Users/Entity/User.php`): - `isAdministrator()` (line 1507): checks the `rol_administrator` flag — full system administrator - `isAdministratorUsers()` (line 1625): checks `rol_edit_user` right — user management module access only When `showAllMembersDatabase=true` reaches `ListConfiguration::getSql()` (at `src/Roles/Entity/ListConfiguration.php:1022-1028`), the generated SQL removes ALL organization filtering: ```php } elseif ($optionsAll['showAllMembersDatabase']) { $sql = 'SELECT DISTINCT ' . $sqlMemLeader . $sqlIdColumns . $sqlColumnNames . ' FROM ' . TBL_USERS . ' ' . $sqlJoin . ' WHERE usr_valid = true ' . $sqlWhere . $sqlOrderBys; } ``` Compare with the default query which includes `cat_org_id = $gCurrentOrgId` to restrict results to the current organization. The cross-org indicator subqueries at line 169 do correctly check `isAdministrator()`, so the `member_other_orga` columns return 0 — but this only affects display indicators, not the actual user data returned. ## PoC **Prerequisites:** An Admidio instance with at least two organizations sharing the same database. A user account in Organization A assigned to a role with `rol_edit_user=1` but `rol_administrator=0`. **Step 1:** Log in as the user manager account and capture the session cookie. **Step 2:** Request all users across all organizations by directly calling the data endpoint: ```bash curl -s -b 'PHPSESSID=<user_manager_session>' \ 'https://target/adm_program/modules/contacts/contacts_data.php?mem_show_filter=3&draw=1&start=0&length=100&search%5Bvalue%5D=' ``` **Expected behavior:** The request should be rejected or return only current-organization users, since the user is not a full administrator and the frontend never offers filter=3 to non-administrators. **Actual behavior:** The endpoint returns a JSON response containing all users from ALL organizations in the database, including: - User UUIDs (`usr_uuid`) - Login names (`login_name`) - Email addresses (`member_email`) - All configured profile fields (names, addresses, phone numbers, etc.) **Step 3:** Verify that users from Organization B (where the attacker has no membership) appear in the results by checking the `member_this_orga` field — it will be `0` for cross-org users. ## Impact In multi-organization Admidio deployments (the primary use case for organization isolation), a user manager in one organization can exfiltrate the complete member directory of all other organizations sharing the same database. Exposed data includes: - Full names and all configured profile fields - Email addresses - Login names (useful for credential attacks) - User UUIDs (useful for targeting other API endpoints) This completely bypasses the multi-tenant organization isolation boundary. The `contacts_show_all` admin setting (intended to control this feature) is also bypassed, meaning even instances where administrators have explicitly disabled cross-org viewing are affected. ## Recommended Fix Change line 235 in `modules/contacts/contacts_data.php` to match the frontend guard at `contacts.php:80`: ```php // Before (vulnerable): } elseif (($getMembersShowFilter === 3) && $gCurrentUser->isAdministratorUsers()) { // After (fixed): } elseif (($getMembersShowFilter === 3) && $gCurrentUser->isAdministrator() && $gSettingsManager->getBool('contacts_show_all')) { ``` Additionally, as defense-in-depth, add an early rejection at the top of the file (after line 59) to block the filter value entirely for unauthorized users: ```php if ($getMembersShowFilter === 3 && (!$gCurrentUser->isAdministrator() || !$gSettingsManager->getBool('contacts_show_all'))) { $getMembersShowFilter = 0; // Fall back to default } ```

CVE-2026-41657 Affected Admidio/admidio Versions

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

References

Frequently Asked Questions

What is CVE-2026-41657?
CVE-2026-41657 is a security vulnerability in Admidio/admidio involving Admidio Exposes Cross-Organization Member Data via Permission Check Mismatch in contacts_data.php under CWE-863: Incorrect Authorization.
Which Admidio/admidio versions are affected?
Affected versions include releases prior to 5.0.9 across affected packages: admidio/admidio.
What version fixes CVE-2026-41657?
Upgrading to 5.0.9 (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.9. Upgrade your dependency to 5.0.9 or later.
What is the CVSS score of CVE-2026-41657?
CVE-2026-41657 has a CVSS v3.1 base score of 4.9 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N.
What CWE is associated with CVE-2026-41657?
CVE-2026-41657 is classified under CWE-863: Incorrect Authorization (The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check.).
How can I check Packagist dependencies for CVE-2026-41657?
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-g8p8-94f2-28gr",
  "modified" : "2026-05-08T19:55:36Z",
  "published" : "2026-04-29T21:44:24Z",
  "aliases" : [ "CVE-2026-41657" ],
  "summary" : "Admidio Exposes Cross-Organization Member Data via Permission Check Mismatch in contacts_data.php",
  "details" : "## Summary\n\nThe `contacts_data.php` endpoint uses a weaker permission check (`isAdministratorUsers()`, requiring only `rol_edit_user=true`) than the frontend UI (`contacts.php`) which correctly requires the stronger `isAdministrator()` (requiring `rol_administrator=true`) and the `contacts_show_all` system setting. A user manager who is not a full administrator can directly request `contacts_data.php?mem_show_filter=3` to retrieve all user records across all organizations in the Admidio instance, bypassing multi-tenant organization isolation.\n\n## Details\n\nThe frontend page `contacts.php` and the backend data endpoint `contacts_data.php` have mismatched authorization checks for the \"show all organizations\" filter (`mem_show_filter=3`).\n\n**Frontend guard** at `modules/contacts/contacts.php:80`:\n```php\nif ($gCurrentUser->isAdministrator() && $gSettingsManager->getBool('contacts_show_all')) {\n    // Only then is filter=3 (\"All Organizations\") shown in the dropdown\n    $selectBoxValues = array(\n        ...\n        '3' => array('3', $gL10n->get('SYS_ALL_CONTACTS'), $gL10n->get('SYS_ALL_ORGANIZATIONS'))\n    );\n}\n```\nThis correctly requires both `isAdministrator()` (`rol_administrator=true`) AND the `contacts_show_all` setting.\n\n**Backend check** at `modules/contacts/contacts_data.php:235`:\n```php\n} elseif (($getMembersShowFilter === 3) && $gCurrentUser->isAdministratorUsers()) {\n    $mainSql = $contactsListConfig->getSql(\n        array(\n            'showAllMembersDatabase' => true,\n            ...\n        )\n    );\n```\nThis only requires `isAdministratorUsers()` which checks `rol_edit_user=true` — a weaker permission available to non-admin \"user manager\" roles. The `contacts_show_all` setting is never checked.\n\n**The critical difference between the two methods** (from `src/Users/Entity/User.php`):\n- `isAdministrator()` (line 1507): checks the `rol_administrator` flag — full system administrator\n- `isAdministratorUsers()` (line 1625): checks `rol_edit_user` right — user management module access only\n\nWhen `showAllMembersDatabase=true` reaches `ListConfiguration::getSql()` (at `src/Roles/Entity/ListConfiguration.php:1022-1028`), the generated SQL removes ALL organization filtering:\n```php\n} elseif ($optionsAll['showAllMembersDatabase']) {\n    $sql = 'SELECT DISTINCT ' . $sqlMemLeader . $sqlIdColumns . $sqlColumnNames . '\n              FROM ' . TBL_USERS . '\n                   ' . $sqlJoin . '\n             WHERE usr_valid = true ' .\n        $sqlWhere .\n        $sqlOrderBys;\n}\n```\n\nCompare with the default query which includes `cat_org_id = $gCurrentOrgId` to restrict results to the current organization.\n\nThe cross-org indicator subqueries at line 169 do correctly check `isAdministrator()`, so the `member_other_orga` columns return 0 — but this only affects display indicators, not the actual user data returned.\n\n## PoC\n\n**Prerequisites:** An Admidio instance with at least two organizations sharing the same database. A user account in Organization A assigned to a role with `rol_edit_user=1` but `rol_administrator=0`.\n\n**Step 1:** Log in as the user manager account and capture the session cookie.\n\n**Step 2:** Request all users across all organizations by directly calling the data endpoint:\n```bash\ncurl -s -b 'PHPSESSID=<user_manager_session>' \\\n  'https://target/adm_program/modules/contacts/contacts_data.php?mem_show_filter=3&draw=1&start=0&length=100&search%5Bvalue%5D='\n```\n\n**Expected behavior:** The request should be rejected or return only current-organization users, since the user is not a full administrator and the frontend never offers filter=3 to non-administrators.\n\n**Actual behavior:** The endpoint returns a JSON response containing all users from ALL organizations in the database, including:\n- User UUIDs (`usr_uuid`)\n- Login names (`login_name`)\n- Email addresses (`member_email`)\n- All configured profile fields (names, addresses, phone numbers, etc.)\n\n**Step 3:** Verify that users from Organization B (where the attacker has no membership) appear in the results by checking the `member_this_orga` field — it will be `0` for cross-org users.\n\n## Impact\n\nIn multi-organization Admidio deployments (the primary use case for organization isolation), a user manager in one organization can exfiltrate the complete member directory of all other organizations sharing the same database. Exposed data includes:\n\n- Full names and all configured profile fields\n- Email addresses\n- Login names (useful for credential attacks)\n- User UUIDs (useful for targeting other API endpoints)\n\nThis completely bypasses the multi-tenant organization isolation boundary. The `contacts_show_all` admin setting (intended to control this feature) is also bypassed, meaning even instances where administrators have explicitly disabled cross-org viewing are affected.\n\n## Recommended Fix\n\nChange line 235 in `modules/contacts/contacts_data.php` to match the frontend guard at `contacts.php:80`:\n\n```php\n// Before (vulnerable):\n} elseif (($getMembersShowFilter === 3) && $gCurrentUser->isAdministratorUsers()) {\n\n// After (fixed):\n} elseif (($getMembersShowFilter === 3) && $gCurrentUser->isAdministrator() && $gSettingsManager->getBool('contacts_show_all')) {\n```\n\nAdditionally, as defense-in-depth, add an early rejection at the top of the file (after line 59) to block the filter value entirely for unauthorized users:\n\n```php\nif ($getMembersShowFilter === 3 && (!$gCurrentUser->isAdministrator() || !$gSettingsManager->getBool('contacts_show_all'))) {\n    $getMembersShowFilter = 0; // Fall back to default\n}\n```",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Packagist",
      "name" : "admidio/admidio"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "5.0.9"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 5.0.8"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/Admidio/admidio/security/advisories/GHSA-g8p8-94f2-28gr"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-41657"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/Admidio/admidio"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/Admidio/admidio/releases/tag/v5.0.9"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-863" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-04-29T21:44:24Z",
    "nvd_published_at" : "2026-05-07T04:16:28Z"
  }
}