CVE-2026-81892: EasyAdmin Custom-Action Dispatcher Authorization Bypass
A low-privilege EasyAdmin user can reach any Symfony route by name simply by passing its route name in a query parameter, skipping path-based access_control rules that were supposed to protect it.

The problem
EasyAdmin funnels all backend traffic through one dashboard URL and dispatches custom actions by reading a routeName query parameter on the kernel.controller event. Symfony's firewall evaluates access_control rules against the dashboard URL before this swap happens, so the target route's own path-based rules are never checked.
Any backend user who can reach the EasyAdmin dashboard and knows the Symfony route name of a more-privileged controller can invoke it directly. The attacker needs no special tooling, only a crafted GET request.
Proof of concept
A working proof-of-concept for CVE-2026-81892 in easycorp/easyadmin-bundle, with the exact payload below.
GET /admin?routeName=app_admin_superadmin_dashboard HTTP/1.1
Host: target.example.com
Cookie: PHPSESSID=<valid_low_privilege_session>
# General pattern (swap in any protected route name):
# GET /admin?routeName=<protected_route_name>[&routeParams[key]=value]
#
# Example using linkToRoute / MenuItem::linkToRoute target:
# GET /admin?routeName=app_restricted_report&routeParams[month]=08The root cause is CWE-639 (Authorization Bypass Through User-Controlled Key): the routeName parameter is attacker-supplied and was passed unvalidated into the controller swap. Symfony's security firewall already ran against /admin, so the path rule protecting /restricted-area was never triggered for the swapped controller.
The patch (commit 6228dfe / 03be45c) adds an explicit access_control re-evaluation inside the custom-action event subscriber before the controller is swapped. If the current user does not pass the check for the target route's path, the request is denied with a 403.
Routes using controller-level #[IsGranted] or denyAccessUnlessGranted() were always safe because those are re-run against the new controller regardless.
The fix
Upgrade to **4.29.16** (4.x branch) or **5.5.1** (5.x branch). As a temporary workaround, add #[IsGranted] or a denyAccessUnlessGranted() call inside every sensitive controller action, since controller-level checks are re-evaluated against the swapped controller and remain effective even on unpatched versions.
Reported by TungNGo02.
Related research
- high · 7.6CVE-2026-54087CVE-2026-54087: EasyAdmin Bundle Stored XSS via FileField and ImageField Upload
- high · 7.6CVE-2026-54180CVE-2026-54180: Laravel Backpack CRUD Cross-Tenant IDOR on Write Operations
- highSolidInvoice: IDOR in Symfony LiveComponents Allows Cross-User API Token and Notification Settings Access
- highCVE-2026-71415CVE-2026-71415: Kirby CMS Unauthenticated Chunked Upload Storage Exhaustion