{"id":"GHSA-xvf4-ch4q-2m24","summary":"StudioCMS REST getUsers Exposes Owner Account Records to Admin Tokens","details":"## Summary\n\nThe REST API `getUsers` endpoint in StudioCMS uses the attacker-controlled `rank` query parameter to decide whether owner accounts should be filtered from the result set. As a result, an admin token can request `rank=owner` and receive owner account records, including IDs, usernames, display names, and email addresses, even though the adjacent `getUser` endpoint correctly blocks admins from viewing owner users. This is an authorization inconsistency inside the same user-management surface.\n\n## Details\n\n### Vulnerable Code Path\n\nFile: `D:/bugcrowd/studiocms/repo/packages/studiocms/frontend/pages/studiocms_api/_handlers/rest-api/v1/secure.ts`, lines 1605-1647\n\n```ts\n.handle(\n    'getUsers',\n    Effect.fn(\n        function* ({ urlParams: { name, rank, username } }) {\n            if (!restAPIEnabled) {\n                return yield* new RestAPIError({ error: 'Endpoint not found' });\n            }\n            const [sdk, user] = yield* Effect.all([SDKCore, CurrentRestAPIUser]);\n\n            if (user.rank !== 'owner' && user.rank !== 'admin') {\n                return yield* new RestAPIError({ error: 'Unauthorized' });\n            }\n\n            const allUsers = yield* sdk.GET.users.all();\n            let data = allUsers.map(...);\n\n            if (rank !== 'owner') {\n                data = data.filter((user) =\u003e user.rank !== 'owner');\n            }\n\n            if (rank) {\n                data = data.filter((user) =\u003e user.rank === rank);\n            }\n\n            return data;\n        },\n```\n\nThe `rank` variable in `if (rank !== 'owner')` is the request query parameter, not the caller's privilege level. An admin can therefore pass `rank=owner`, skip the owner-filtering branch, and then have the second `if (rank)` branch return only owner accounts.\n\n### Adjacent Endpoint Shows Intended Security Boundary\n\nFile: `D:/bugcrowd/studiocms/repo/packages/studiocms/frontend/pages/studiocms_api/_handlers/rest-api/v1/secure.ts`, lines 1650-1710\n\n```ts\nconst existingUserRankIndex = availablePermissionRanks.indexOf(existingUserRank);\nconst loggedInUserRankIndex = availablePermissionRanks.indexOf(user.rank);\n\nif (loggedInUserRankIndex \u003c= existingUserRankIndex) {\n    return yield* new RestAPIError({\n        error: 'Unauthorized to view user with higher rank',\n    });\n}\n```\n\n`getUser` correctly blocks an admin from viewing an owner record. `getUsers` bypasses that boundary for bulk enumeration.\n\n### Sensitive Fields Returned\n\nThe `getUsers` response includes:\n\n- `id`\n- `email`\n- `name`\n- `username`\n- `rank`\n- timestamps and profile URL/avatar fields when present\n\nThis is enough to enumerate all owner accounts and target them for phishing, social engineering, or follow-on attacks against out-of-band workflows.\n\n## PoC\n\n### HTTP PoC\n\nUse any admin-level REST API token:\n\n```bash\ncurl -X GET 'http://localhost:4321/studiocms_api/rest/v1/secure/users?rank=owner' \\\n  -H 'Authorization: Bearer \u003cadmin-api-token\u003e'\n```\n\nExpected behavior:\n- owner records should be excluded for admin callers, consistent with `getUser`\n\nActual behavior:\n- the response contains owner user objects, including email addresses and user IDs\n\n### Local Validation of the Exact Handler Logic\n\nI validated the filtering logic locally with the same conditions used by `getUsers` and `getUser`.\n\nObserved output:\n\n```json\n{\n  \"admin_getUsers_rank_owner\": [\n    {\n      \"email\": \"owner@example.test\",\n      \"id\": \"owner-1\",\n      \"name\": \"Site Owner\",\n      \"rank\": \"owner\",\n      \"username\": \"owner1\"\n    }\n  ],\n  \"admin_getUser_owner\": \"Unauthorized to view user with higher rank\"\n}\n```\n\nThis demonstrates the authorization mismatch clearly:\n- bulk listing with `rank=owner` exposes owner records\n- direct access to a single owner record is denied\n\n## Impact\n\n- **Owner Account Enumeration:** Admin tokens can recover owner user IDs, usernames, display names, and email addresses.\n- **Authorization Boundary Bypass:** The REST collection endpoint bypasses the stricter per-record rank check already implemented by `getUser`.\n- **Chaining Value:** Exposed owner contact data can support phishing, account-targeting, and admin-to-owner pivot attempts in deployments that treat owner identities as higher-trust principals.\n\n## Recommended Fix\n\nApply rank filtering based on the caller's role, not on the request query parameter, and reuse the same privilege rule as `getUser`.\n\nExample fix:\n\n```ts\nconst loggedInUserRankIndex = availablePermissionRanks.indexOf(user.rank);\n\ndata = data.filter((candidate) =\u003e {\n    const candidateRankIndex = availablePermissionRanks.indexOf(candidate.rank);\n    return loggedInUserRankIndex \u003e candidateRankIndex;\n});\n\nif (rank) {\n    data = data.filter((candidate) =\u003e candidate.rank === rank);\n}\n```\n\nAt minimum, replace:\n\n```ts\nif (rank !== 'owner') {\n    data = data.filter((user) =\u003e user.rank !== 'owner');\n}\n```\n\nwith a check tied to `user.rank` rather than the query parameter.","aliases":["CVE-2026-32638"],"modified":"2026-03-18T22:01:30.322032Z","published":"2026-03-16T16:37:42Z","database_specific":{"cwe_ids":["CWE-639"],"severity":"LOW","github_reviewed":true,"github_reviewed_at":"2026-03-16T16:37:42Z","nvd_published_at":"2026-03-18T21:16:26Z"},"references":[{"type":"WEB","url":"https://github.com/withstudiocms/studiocms/security/advisories/GHSA-xvf4-ch4q-2m24"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-32638"},{"type":"WEB","url":"https://github.com/withstudiocms/studiocms/commit/aebe8bcb3618bb07c6753e3f5c982c1fe6adea64"},{"type":"PACKAGE","url":"https://github.com/withstudiocms/studiocms"},{"type":"WEB","url":"https://github.com/withstudiocms/studiocms/releases/tag/studiocms@0.4.4"}],"affected":[{"package":{"name":"studiocms","ecosystem":"npm","purl":"pkg:npm/studiocms"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.4.4"}]}],"database_specific":{"last_known_affected_version_range":"\u003c= 0.4.3","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/03/GHSA-xvf4-ch4q-2m24/GHSA-xvf4-ch4q-2m24.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"}]}