Broken Access Control in Backend AJAX Routes
Description
Missing authorization checks in the Backend Routing of TYPO3 CMS versions 9.0.0‑9.5.54, 10.0.0‑10.4.53, 11.0.0‑11.5.47, 12.0.0‑12.4.36, and 13.0.0‑13.4.17 allow backend users to directly invoke AJAX backend routes without having access to the corresponding backend modules.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Missing authorization checks in TYPO3 CMS backend AJAX routes allow authenticated users to access data without proper module permissions.
CVE-2025-59017 describes a missing authorization vulnerability in TYPO3 CMS versions 9.0.0 through 13.4.17 [1]. The root cause is that AJAX backend routes associated with TYPO3 backend modules were not protected by the same permission checks that guard the modules themselves [1][4]. Without explicit binding to a module's access restrictions, these routes could be invoked by any authenticated backend user regardless of their module permissions [1][4].
Exploitation
An authenticated backend user with low privileges can directly call these AJAX routes by crafting HTTP requests to known endpoints (e.g., /dashboard/dashboards/get, /dashboard/widget/get) without having access to the corresponding backend module [2][4]. The attack requires valid backend user authentication but no special module rights [4]. The exploitation is network-based (AV:N) and does not require user interaction beyond the initial authentication [4].
Impact
Successful exploitation allows an attacker to read, modify, or delete data associated with affected backend modules, leading to potential information disclosure, data integrity compromise, and service disruption. The CVSS v4.0 vector (CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L) confirms low but real impacts on confidentiality, integrity, and availability [4].
Mitigation
TYPO3 has released patched versions: 9.5.55 ELTS, 10.4.54 ELTS, 11.5.48 ELTS, 12.4.37 LTS, and 13.4.18 LTS [4]. The fix introduces the inheritAccessFromModule route property, which explicitly binds AJAX routes to a backend module's permissions [2][3][4]. Administrators are advised to update immediately and follow the TYPO3 Security Guide for further hardening [4].
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
typo3/cms-workspacesPackagist | >= 9.0.0, < 12.4.37 | 12.4.37 |
typo3/cms-workspacesPackagist | >= 10.0.0, < 12.4.37 | 12.4.37 |
typo3/cms-workspacesPackagist | >= 11.0.0, < 12.4.37 | 12.4.37 |
typo3/cms-workspacesPackagist | >= 12.0.0, < 12.4.37 | 12.4.37 |
typo3/cms-workspacesPackagist | >= 13.0.0, < 13.4.18 | 13.4.18 |
typo3/cms-recyclerPackagist | >= 9.0.0, < 12.4.37 | 12.4.37 |
typo3/cms-recyclerPackagist | >= 10.0.0, < 12.4.37 | 12.4.37 |
typo3/cms-recyclerPackagist | >= 11.0.0, < 12.4.37 | 12.4.37 |
typo3/cms-recyclerPackagist | >= 12.0.0, < 12.4.37 | 12.4.37 |
typo3/cms-recyclerPackagist | >= 13.0.0, < 13.4.18 | 13.4.18 |
typo3/cms-dashboardPackagist | >= 10.0.0, < 12.4.37 | 12.4.37 |
typo3/cms-dashboardPackagist | >= 11.0.0, < 12.4.37 | 12.4.37 |
typo3/cms-dashboardPackagist | >= 12.0.0, < 12.4.37 | 12.4.37 |
typo3/cms-dashboardPackagist | >= 13.0.0, < 13.4.18 | 13.4.18 |
typo3/cms-beuserPackagist | >= 13.0.0, < 13.4.18 | 13.4.18 |
typo3/cms-beuserPackagist | >= 12.0.0, < 12.4.37 | 12.4.37 |
typo3/cms-beuserPackagist | >= 11.0.0, < 12.4.37 | 12.4.37 |
typo3/cms-beuserPackagist | >= 10.0.0, < 12.4.37 | 12.4.37 |
typo3/cms-beuserPackagist | >= 9.0.0, < 12.4.37 | 12.4.37 |
typo3/cms-backendPackagist | >= 9.0.0, < 12.4.37 | 12.4.37 |
typo3/cms-backendPackagist | >= 10.0.0, < 12.4.37 | 12.4.37 |
typo3/cms-backendPackagist | >= 11.0.0, < 12.4.37 | 12.4.37 |
typo3/cms-backendPackagist | >= 12.0.0, < 12.4.37 | 12.4.37 |
typo3/cms-backendPackagist | >= 13.0.0, < 13.4.18 | 13.4.18 |
Affected products
2- TYPO3/TYPO3 CMSv5Range: 9.0.0
Patches
5eb9b0c14a514[SECURITY] Inherit access to module-related AJAX routes from modules
1 file changed · +1 −0
Configuration/Backend/AjaxRoutes.php+1 −0 modified@@ -8,5 +8,6 @@ 'user_access_permissions' => [ 'path' => '/users/access/permissions', 'target' => \TYPO3\CMS\Beuser\Controller\PermissionController::class . '::handleAjaxRequest', + 'inheritAccessFromModule' => 'permissions_pages', ], ];
0aedf33d910b[SECURITY] Inherit access to module-related AJAX routes from modules
4 files changed · +61 −12
Classes/Middleware/BackendModuleValidator.php+14 −4 modified@@ -31,6 +31,7 @@ use TYPO3\CMS\Backend\Routing\UriBuilder; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Http\RedirectResponse; +use TYPO3\CMS\Core\Http\Response; use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\Messaging\FlashMessage; use TYPO3\CMS\Core\Messaging\FlashMessageQueue; @@ -68,10 +69,19 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $inaccessibleSubModule = null; $ensureToPersistUserSettings = false; $backendUser = $GLOBALS['BE_USER'] ?? null; - if (!$backendUser - || !$route->hasOption('module') - || !(($module = $route->getOption('module')) instanceof ModuleInterface) - ) { + + if (!$backendUser) { + return $handler->handle($request); + } + + // Exit if access to module was denied using module access inheritance check + $inheritAccessFromModule = $route->getOption('inheritAccessFromModule'); + if ($inheritAccessFromModule !== null && !$this->moduleProvider->accessGranted($inheritAccessFromModule, $backendUser)) { + return new Response(null, 403); + } + + $module = $route->getOption('module'); + if (!$module instanceof ModuleInterface) { return $handler->handle($request); }
Configuration/Backend/AjaxRoutes.php+9 −0 modified@@ -17,6 +17,7 @@ 'path' => '/resource/rename', 'methods' => ['POST'], 'target' => Controller\Resource\ResourceController::class . '::renameResourceAction', + 'inheritAccessFromModule' => 'media_management', ], // Link resource @@ -30,12 +31,14 @@ 'file_process' => [ 'path' => '/file/process', 'target' => Controller\File\FileController::class . '::processAjaxRequest', + 'inheritAccessFromModule' => 'media_management', ], // Check if file exists 'file_exists' => [ 'path' => '/file/exists', 'target' => Controller\File\FileController::class . '::fileExistsInFolderAction', + 'inheritAccessFromModule' => 'media_management', ], // Get details of a file reference in FormEngine @@ -93,6 +96,7 @@ 'site_configuration_inline_create' => [ 'path' => '/siteconfiguration/inline/create', 'target' => Controller\SiteInlineAjaxController::class . '::newInlineChildAction', + 'inheritAccessFromModule' => 'site_configuration', ], // Validate slug input @@ -105,6 +109,7 @@ 'site_configuration_inline_details' => [ 'path' => '/siteconfiguration/inline/details', 'target' => Controller\SiteInlineAjaxController::class . '::openInlineChildAction', + 'inheritAccessFromModule' => 'site_configuration', ], // Add a flex form section container @@ -353,18 +358,21 @@ 'page_languages' => [ 'path' => '/records/localize/get-languages', 'target' => Controller\Page\LocalizationController::class . '::getUsedLanguagesInPage', + 'inheritAccessFromModule' => 'web_layout', ], // Get summary of records to localize 'records_localize_summary' => [ 'path' => '/records/localize/summary', 'target' => Controller\Page\LocalizationController::class . '::getRecordLocalizeSummary', + 'inheritAccessFromModule' => 'web_layout', ], // Localize the records 'records_localize' => [ 'path' => '/records/localize', 'target' => Controller\Page\LocalizationController::class . '::localizeRecords', + 'inheritAccessFromModule' => 'web_layout', ], // column selector @@ -407,6 +415,7 @@ 'access' => 'systemMaintainer', 'path' => '/security/csp/control', 'target' => \TYPO3\CMS\Backend\Security\ContentSecurityPolicy\CspAjaxController::class . '::handleRequest', + 'inheritAccessFromModule' => 'tools_csp', ], 'sudo_mode_control' => [
Tests/Functional/Fixtures/be_users_core.csv+7 −7 modified@@ -1,9 +1,9 @@ be_users -,uid,pid,tstamp,username,password,admin,disable,starttime,endtime,options,crdate,workspace_perms,deleted,TSconfig,lastlogin,workspace_id,usergroup,mfa -,1,0,1366642540,admin,$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1,1,0,0,0,0,1366642540,1,0,,1371033743,0,, -,2,0,1366642540,editor,$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1,0,0,0,0,3,1366642540,1,0,,1371033743,0,1, -,3,0,1366642540,editor_with_groups,$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1,0,0,0,0,3,1366642540,1,0,"test.default.userValue = from_user_3",1371033743,0,"1,2,4,6", -,4,0,1366642540,mfa_user,$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1,0,0,0,0,3,1366642540,1,0,,1371033743,0,,"{""totp"":{""secret"":""KRMVATZTJFZUC53FONXW2ZJB"",""active"":true,""attempts"":2}}" -,5,0,1366642540,mfa_admin_locked,$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1,1,0,0,0,3,1366642540,1,0,,1371033743,0,,"{""totp"":{""secret"":""KRMVATZTJFZUC53FONXW2ZJB"",""active"":true,""attempts"":2},""recovery-codes"":{""active"":true,""attempts"":3,""codes"":[]}}" +,uid,pid,tstamp,username,password,admin,disable,starttime,endtime,options,crdate,workspace_perms,deleted,TSconfig,lastlogin,workspace_id,usergroup,mfa,userMods +,1,0,1366642540,admin,$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1,1,0,0,0,0,1366642540,1,0,,1371033743,0,,, +,2,0,1366642540,editor,$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1,0,0,0,0,3,1366642540,1,0,,1371033743,0,1,, +,3,0,1366642540,editor_with_groups,$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1,0,0,0,0,3,1366642540,1,0,"test.default.userValue = from_user_3",1371033743,0,"1,2,4,6",,"web_layout" +,4,0,1366642540,mfa_user,$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1,0,0,0,0,3,1366642540,1,0,,1371033743,0,,"{""totp"":{""secret"":""KRMVATZTJFZUC53FONXW2ZJB"",""active"":true,""attempts"":2}}", +,5,0,1366642540,mfa_admin_locked,$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1,1,0,0,0,3,1366642540,1,0,,1371033743,0,,"{""totp"":{""secret"":""KRMVATZTJFZUC53FONXW2ZJB"",""active"":true,""attempts"":2},""recovery-codes"":{""active"":true,""attempts"":3,""codes"":[]}}", ,6,0,1366642540,editor with typoscript,$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1,0,0,0,0,3,1366642540,1,0,"options.impexp.enableImportForNonAdminUser = 1 -options.impexp.enableExportForNonAdminUser = 1",1371033743,0,1, +options.impexp.enableExportForNonAdminUser = 1",1371033743,0,1,,
Tests/Functional/Middleware/BackendModuleValidatorTest.php+31 −1 modified@@ -47,7 +47,7 @@ protected function setUp(): void { parent::setUp(); - $this->importCSVDataSet(__DIR__ . '/../Fixtures/be_users.csv'); + $this->importCSVDataSet(__DIR__ . '/../Fixtures/be_users_core.csv'); $backendUser = $this->setUpBackendUser(1); $GLOBALS['LANG'] = $this->get(LanguageServiceFactory::class)->createFromUserPreferences($backendUser); @@ -72,6 +72,36 @@ public function handle(ServerRequestInterface $request): ResponseInterface }; } + #[Test] + public function processReturnsForbiddenResponseIfModuleInheritanceAccessCheckFails(): void + { + $this->setUpBackendUser(2); + + $GLOBALS['TYPO3_REQUEST'] = $request = $this->request->withAttribute( + 'route', + new Route('/some/route', ['inheritAccessFromModule' => 'web_layout']), + ); + + $response = $this->subject->process($request, $this->requestHandler); + + self::assertSame(403, $response->getStatusCode()); + } + + #[Test] + public function processReturnsOkResponseIfModuleInheritanceAccessCheckIsSuccessful(): void + { + $this->setUpBackendUser(3); + + $GLOBALS['TYPO3_REQUEST'] = $request = $this->request->withAttribute( + 'route', + new Route('/some/route', ['inheritAccessFromModule' => 'web_layout']), + ); + + $response = $this->subject->process($request, $this->requestHandler); + + self::assertSame(200, $response->getStatusCode()); + } + #[Test] public function moduleIsAddedToRequest(): void {
582006c6bdf2[SECURITY] Inherit access to module-related AJAX routes from modules
1 file changed · +12 −0
Configuration/Backend/AjaxRoutes.php+12 −0 modified@@ -8,66 +8,78 @@ 'path' => '/dashboard/dashboards/get', 'target' => DashboardAjaxController::class . '::getDashboards', 'methods' => ['GET'], + 'inheritAccessFromModule' => 'dashboard', ], 'dashboard_dashboard_add' => [ 'path' => '/dashboard/dashboard/add', 'target' => DashboardAjaxController::class . '::addDashboard', 'methods' => ['POST'], + 'inheritAccessFromModule' => 'dashboard', ], 'dashboard_dashboard_edit' => [ 'path' => '/dashboard/dashboard/edit', 'target' => DashboardAjaxController::class . '::editDashboard', 'methods' => ['POST'], + 'inheritAccessFromModule' => 'dashboard', ], 'dashboard_dashboard_update' => [ 'path' => '/dashboard/dashboard/update', 'target' => DashboardAjaxController::class . '::updateDashboard', 'methods' => ['POST'], + 'inheritAccessFromModule' => 'dashboard', ], 'dashboard_dashboard_delete' => [ 'path' => '/dashboard/dashboard/delete', 'target' => DashboardAjaxController::class . '::deleteDashboard', 'methods' => ['POST'], + 'inheritAccessFromModule' => 'dashboard', ], // Presets 'dashboard_presets_get' => [ 'path' => '/dashboard/presets/get', 'target' => DashboardAjaxController::class . '::getPresets', 'methods' => ['GET'], + 'inheritAccessFromModule' => 'dashboard', ], // Categories 'dashboard_categories_get' => [ 'path' => '/dashboard/categories/get', 'target' => DashboardAjaxController::class . '::getCategories', 'methods' => ['GET'], + 'inheritAccessFromModule' => 'dashboard', ], // Widgets 'dashboard_widget_get' => [ 'path' => '/dashboard/widget/get', 'target' => DashboardAjaxController::class . '::getWidget', 'methods' => ['GET'], + 'inheritAccessFromModule' => 'dashboard', ], 'dashboard_widget_add' => [ 'path' => '/dashboard/widget/add', 'target' => DashboardAjaxController::class . '::addWidget', 'methods' => ['POST'], + 'inheritAccessFromModule' => 'dashboard', ], 'dashboard_widget_remove' => [ 'path' => '/dashboard/widget/remove', 'target' => DashboardAjaxController::class . '::removeWidget', 'methods' => ['POST'], + 'inheritAccessFromModule' => 'dashboard', ], 'dashboard_widget_settings_get' => [ 'path' => '/dashboard/widget/settings/get', 'target' => DashboardAjaxController::class . '::getWidgetSettings', 'methods' => ['GET'], + 'inheritAccessFromModule' => 'dashboard', ], 'dashboard_widget_settings_update' => [ 'path' => '/dashboard/widget/settings/update', 'target' => DashboardAjaxController::class . '::updateWidgetSettings', 'methods' => ['POST'], + 'inheritAccessFromModule' => 'dashboard', ], ];
43475578eb1d[SECURITY] Inherit access to module-related AJAX routes from modules
1 file changed · +1 −0
Configuration/Backend/AjaxRoutes.php+1 −0 modified@@ -8,5 +8,6 @@ 'recycler' => [ 'path' => '/recycler', 'target' => \TYPO3\CMS\Recycler\Controller\RecyclerAjaxController::class . '::dispatch', + 'inheritAccessFromModule' => 'recycler', ], ];
322225080439[SECURITY] Inherit access to module-related AJAX routes from modules
1 file changed · +1 −0
Configuration/Backend/AjaxRoutes.php+1 −0 modified@@ -12,5 +12,6 @@ 'workspace_dispatch' => [ 'path' => '/workspace/dispatch', 'target' => \TYPO3\CMS\Workspaces\Controller\WorkspacesAjaxController::class . '::dispatch', + 'inheritAccessFromModule' => 'workspaces_admin', ], ];
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- github.com/advisories/GHSA-2fhw-2j7m-mr4mghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-59017ghsaADVISORY
- typo3.org/security/advisory/typo3-core-sa-2025-021ghsavendor-advisoryWEB
- github.com/TYPO3-CMS/backend/commit/0aedf33d910bceafc2ed0e715743cc0d30124501ghsaWEB
- github.com/TYPO3-CMS/beuser/commit/eb9b0c14a514a7aada8a2aa30e57696e286044c7ghsaWEB
- github.com/TYPO3-CMS/dashboard/commit/582006c6bdf251160001eee6624901baccdcfcd2ghsaWEB
- github.com/TYPO3-CMS/recycler/commit/43475578eb1d9fa3b765537c96bcdf48582ee53bghsaWEB
- github.com/TYPO3-CMS/workspaces/commit/32222508043940f9073c338d4205c730a2e02070ghsaWEB
News mentions
0No linked articles in our index yet.