GHSA-g8p8-94f2-28gr
MEDIUMGHSA-g8p8-94f2-28gr is a medium-severity (CVSS 4.9) CWE-863 vulnerability in admidio/admidio. O3 Security confirms whether GHSA-g8p8-94f2-28gr is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.
Admidio Exposes Cross-Organization Member Data via Permission Check Mismatch in contacts_data.php
Exploitation Status
No confirmed exploitation observed yet
- CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.
Exploitation and automatability from CISA’s SSVC triage for GHSA-g8p8-94f2-28gr.
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
How urgent is this, really
GHSA-g8p8-94f2-28gr plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.
Where this sits among everything scored
Of 356,530 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.
Real-World Exposure
admidio/admidioReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Packagist packages — download data is not available via public APIs for these ecosystems.
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 therol_administratorflag — full system administratorisAdministratorUsers()(line 1625): checksrol_edit_userright — 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
}
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐘Packagist | admidio/admidio | all versions | 5.0.9 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for admidio/admidio. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update admidio/admidio to 5.0.9 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-g8p8-94f2-28gr is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-g8p8-94f2-28gr is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-g8p8-94f2-28gr. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-g8p8-94f2-28gr in your dependencies?
O3 detects GHSA-g8p8-94f2-28gr across Packagist dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.