GIVE YOUR AGENT THE EVIDENCE

Set up in your agent.

One prompt to get started. Paste it into your coding agent to connect cve.new through MCP and bring vulnerability context into your workflow.

Your agent needs remote MCP support and access to https://cve.new/mcp.

Read the setup guide

GIVE YOUR AGENT THE EVIDENCE

Investigate in your agent.

Turn this advisory into a focused investigation. Ask your agent to check your project’s versions, explain the evidence, and suggest a compatible fix.

Your agent needs remote MCP support and access to https://cve.new/mcp.

Read the setup guide

Check your stack
GHSA-g8p8-94f2-28grmedium4.9

Admidio Exposes Cross-Organization Member Data via Permission Check Mismatch in contacts_data.php

Impact

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.

Source excerpt · OSV
Applies when

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. '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. )
    );

This only requires isAdministratorUsers() which checks rol_edit_user=true — a weaker permission available to non-admin "user manager" roles.

Affected packages

Start with the recorded fix for your release branch. Check your installed version and test compatibility before upgrading. Fixing this advisory does not rule out other vulnerabilities in the release.

Affected rangeRecorded fix
Earlier releases before 5.0.9Ecosystem versions5.0.9

Version evidence: OSV · GHSA-g8p8-94f2-28gr

View original version evidence (54 explicit versions)

Original source description

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:

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:

} 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:

} 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:

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:

// 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:

if ($getMembersShowFilter === 3 && (!$gCurrentUser->isAdministrator() || !$gSettingsManager->getBool('contacts_show_all'))) {
    $getMembersShowFilter = 0; // Fall back to default
}

References