Information Disclosure via Export Module in TYPO3 CMS
Description
TYPO3 is an open source web content management system. Prior to versions 7.6.57 ELTS, 8.7.47 ELTS, 9.5.34 ELTS, 10.4.29, and 11.5.11, the export functionality fails to limit the result set to allowed columns of a particular database table. This way, authenticated users can export internal details of database tables they already have access to. TYPO3 versions 7.6.57 ELTS, 8.7.47 ELTS, 9.5.34 ELTS, 10.4.29, 11.5.11 fix the problem described above. In order to address this issue, access to mentioned export functionality is completely denied for regular backend users.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
TYPO3 export functionality fails to limit columns, allowing authenticated backend users to export internal database details they already have access to.
Vulnerability
CVE-2022-31046 is an information disclosure vulnerability in the TYPO3 content management system. The export feature improperly restricts the result set to allowed columns of a database table, enabling authenticated users to export internal details of tables they are already permitted to access. The root cause is a missing column permission check during CSV or similar export operations. [1]
Attack
Vector An attacker must have a valid backend user account with at least some level of read access to the affected database tables. No special privileges beyond normal table access are required. The export functionality can then be used to retrieve additional columns or internal system fields that are not normally visible in the user interface, exposing sensitive data. The attack is performed within the web application's backend interface and does not require network-level manipulation. [1]
Impact
Successful exploitation allows an authenticated user to export internal details of database tables they already have access to. This can lead to unintended disclosure of configuration settings, user data, or other hidden fields, potentially aiding further attacks or exposing sensitive information. The vulnerability does not allow privilege escalation but can amplify the damage from existing access. [1]
Mitigation
The issue is fixed in TYPO3 versions 7.6.57 ELTS, 8.7.47 ELTS, 9.5.34 ELTS, 10.4.29, and 11.5.11. The patch adds an ImportExportFilter to restrict file and folder name filters during export, and completely denies access to the export functionality for regular backend users. [2][4] Users must upgrade to one of these versions to fully remediate the vulnerability.
AI Insight generated on May 21, 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-corePackagist | >= 7.0.0, < 7.6.57 | 7.6.57 |
typo3/cms-corePackagist | >= 8.0.0, < 8.7.47 | 8.7.47 |
typo3/cms-corePackagist | >= 9.0.0, < 9.5.35 | 9.5.35 |
typo3/cms-corePackagist | >= 10.0.0, < 10.4.29 | 10.4.29 |
typo3/cms-corePackagist | >= 11.0.0, < 11.5.11 | 11.5.11 |
typo3/cmsPackagist | >= 10.0.0, < 10.4.29 | 10.4.29 |
typo3/cmsPackagist | >= 11.0.0, < 11.5.11 | 11.5.11 |
Affected products
4- osv-coords3 versions
>= 7.0.0, < 7.6.57+ 2 more
- (no CPE)range: >= 7.0.0, < 7.6.57
- (no CPE)range: >= 10.0.0, < 10.4.29
- (no CPE)range: >= 7.0.0, < 7.6.57
Patches
17447a3d12830[SECURITY] Restrict export functionality to allowed users
10 files changed · +236 −33
typo3/sysext/core/Classes/Authentication/BackendUserAuthentication.php+22 −0 modified@@ -2289,4 +2289,26 @@ public function isMfaSetupRequired(): bool || ($globalConfig === 3 && $isAdmin) || ($globalConfig === 4 && $this->isSystemMaintainer()); } + + /** + * Returns if import functionality is available for current user + * + * @internal + */ + public function isImportEnabled(): bool + { + return $this->isAdmin() + || ($this->getTSConfig()['options.']['impexp.']['enableImportForNonAdminUser'] ?? false); + } + + /** + * Returns if export functionality is available for current user + * + * @internal + */ + public function isExportEnabled(): bool + { + return $this->isAdmin() + || ($this->getTSConfig()['options.']['impexp.']['enableExportForNonAdminUser'] ?? false); + } }
typo3/sysext/core/Classes/Resource/Filter/ImportExportFilter.php+55 −0 added@@ -0,0 +1,55 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3\CMS\Core\Resource\Filter; + +use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; +use TYPO3\CMS\Core\Resource\Driver\DriverInterface; + +/** + * Utility methods for filtering filenames stored in `importexport` temporary folder. + * Albeit this filter is in the scope of `ext:impexp`, it is located in `ext:core` to + * apply filters on left-over fragments, even when `ext:impexp` is not installed. + * + * @internal + */ +class ImportExportFilter +{ + /** + * Filter method that checks if a directory or a file in such directory belongs to the temp directory of EXT:impexp + * and the user has "export" permissions. + */ + public static function filterImportExportFilesAndFolders(string $itemName, string $itemIdentifier, string $parentIdentifier, array $additionalInformation, DriverInterface $driverInstance) + { + // + `_temp_` is hard-coded in `BackendUserAuthentication::getDefaultUploadTemporaryFolder()` + // + `importexport` is hard-coded in `ImportExport::createDefaultImportExportFolder()` + $importExportFolderSubPath = '/_temp_/importexport/'; + if (str_ends_with($parentIdentifier, $importExportFolderSubPath) || str_contains($itemIdentifier, $importExportFolderSubPath)) { + $backendUser = self::getBackendUser(); + if ($backendUser === null || !$backendUser->isExportEnabled()) { + return -1; + } + } + + return true; + } + + protected static function getBackendUser(): ?BackendUserAuthentication + { + return $GLOBALS['BE_USER'] ?? null; + } +}
typo3/sysext/core/Classes/Resource/ResourceStorage.php+22 −7 modified@@ -74,6 +74,7 @@ use TYPO3\CMS\Core\Resource\Exception\ResourcePermissionsUnavailableException; use TYPO3\CMS\Core\Resource\Exception\UploadException; use TYPO3\CMS\Core\Resource\Exception\UploadSizeException; +use TYPO3\CMS\Core\Resource\Filter\ImportExportFilter; use TYPO3\CMS\Core\Resource\Index\FileIndexRepository; use TYPO3\CMS\Core\Resource\Index\Indexer; use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry; @@ -1517,14 +1518,27 @@ public function resetFileAndFolderNameFiltersToDefault() $this->fileAndFolderNameFilters = $GLOBALS['TYPO3_CONF_VARS']['SYS']['fal']['defaultFilterCallbacks']; } + /** + * Returns a filter for files generated by EXT:impexp + * + * @return array<int, ImportExportFilter|string> + * @internal + */ + public function getImportExportFilter(): array + { + $filter = GeneralUtility::makeInstance(ImportExportFilter::class); + + return [$filter, 'filterImportExportFilesAndFolders']; + } + /** * Returns the file and folder name filters used by this storage. * * @return array */ public function getFileAndFolderNameFilters() { - return $this->fileAndFolderNameFilters; + return array_merge($this->fileAndFolderNameFilters, [$this->getImportExportFilter()]); } /** @@ -1589,7 +1603,7 @@ public function getFilesInFolder(Folder $folder, $start = 0, $maxNumberOfItems = $rows = $this->getFileIndexRepository()->findByFolder($folder); - $filters = $useFilters == true ? $this->fileAndFolderNameFilters : []; + $filters = $useFilters == true ? $this->getFileAndFolderNameFilters() : []; $fileIdentifiers = array_values($this->driver->getFilesInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters, $sort, $sortRev)); $items = []; @@ -1619,7 +1633,7 @@ public function getFilesInFolder(Folder $folder, $start = 0, $maxNumberOfItems = */ public function getFileIdentifiersInFolder($folderIdentifier, $useFilters = true, $recursive = false) { - $filters = $useFilters == true ? $this->fileAndFolderNameFilters : []; + $filters = $useFilters == true ? $this->getFileAndFolderNameFilters() : []; return $this->driver->getFilesInFolder($folderIdentifier, 0, 0, $recursive, $filters); } @@ -1633,7 +1647,7 @@ public function getFileIdentifiersInFolder($folderIdentifier, $useFilters = true public function countFilesInFolder(Folder $folder, $useFilters = true, $recursive = false) { $this->assureFolderReadPermission($folder); - $filters = $useFilters ? $this->fileAndFolderNameFilters : []; + $filters = $useFilters ? $this->getFileAndFolderNameFilters() : []; return $this->driver->countFilesInFolder($folder->getIdentifier(), $recursive, $filters); } @@ -1645,7 +1659,7 @@ public function countFilesInFolder(Folder $folder, $useFilters = true, $recursiv */ public function getFolderIdentifiersInFolder($folderIdentifier, $useFilters = true, $recursive = false) { - $filters = $useFilters == true ? $this->fileAndFolderNameFilters : []; + $filters = $useFilters == true ? $this->getFileAndFolderNameFilters() : []; return $this->driver->getFoldersInFolder($folderIdentifier, 0, 0, $recursive, $filters); } @@ -2417,7 +2431,7 @@ public function getFolderInFolder($folderName, Folder $parentFolder, $returnInac */ public function getFoldersInFolder(Folder $folder, $start = 0, $maxNumberOfItems = 0, $useFilters = true, $recursive = false, $sort = '', $sortRev = false) { - $filters = $useFilters == true ? $this->fileAndFolderNameFilters : []; + $filters = $useFilters == true ? $this->getFileAndFolderNameFilters() : []; $folderIdentifiers = $this->driver->getFoldersInFolder($folder->getIdentifier(), $start, $maxNumberOfItems, $recursive, $filters, $sort, $sortRev); @@ -2428,6 +2442,7 @@ public function getFoldersInFolder(Folder $folder, $start = 0, $maxNumberOfItems unset($folderIdentifiers[$processingIdentifier]); } } + $folders = []; foreach ($folderIdentifiers as $folderIdentifier) { $folders[$folderIdentifier] = $this->getFolder($folderIdentifier, true); @@ -2445,7 +2460,7 @@ public function getFoldersInFolder(Folder $folder, $start = 0, $maxNumberOfItems public function countFoldersInFolder(Folder $folder, $useFilters = true, $recursive = false) { $this->assureFolderReadPermission($folder); - $filters = $useFilters ? $this->fileAndFolderNameFilters : []; + $filters = $useFilters ? $this->getFileAndFolderNameFilters() : []; return $this->driver->countFoldersInFolder($folder->getIdentifier(), $recursive, $filters); }
typo3/sysext/core/Tests/Acceptance/Application/Impexp/UsersCest.php+5 −5 modified@@ -52,7 +52,7 @@ public function _before(ApplicationTester $I): void /** * @throws \Exception */ - public function doNotShowImportInContextMenuForNonAdminUser(ApplicationTester $I, PageTree $pageTree): void + public function doNotShowImportAndExportInContextMenuForNonAdminUser(ApplicationTester $I, PageTree $pageTree): void { $selectedPageTitle = 'Root'; $selectedPageIcon = '//*[text()=\'' . $selectedPageTitle . '\']/../*[contains(@class, \'node-icon-container\')]'; @@ -65,7 +65,7 @@ public function doNotShowImportInContextMenuForNonAdminUser(ApplicationTester $I $I->click($selectedPageIcon); $this->selectInContextMenu($I, [$this->contextMenuMore]); $I->waitForElementVisible('#contentMenu1', 5); - $I->seeElement($this->contextMenuExport); + $I->dontSeeElement($this->contextMenuExport); $I->dontSeeElement($this->contextMenuImport); $I->useExistingSession('admin'); @@ -74,19 +74,19 @@ public function doNotShowImportInContextMenuForNonAdminUser(ApplicationTester $I /** * @throws \Exception */ - public function showImportInContextMenuForNonAdminUserIfFlagSet(ApplicationTester $I): void + public function showImportExportInContextMenuForNonAdminUserIfFlagSet(ApplicationTester $I): void { $selectedPageTitle = 'Root'; $selectedPageIcon = '//*[text()=\'' . $selectedPageTitle . '\']/../*[contains(@class, \'node-icon-container\')]'; - $this->setUserTsConfig($I, 2, 'options.impexp.enableImportForNonAdminUser = 1'); + $this->setUserTsConfig($I, 2, "options.impexp.enableImportForNonAdminUser = 1\noptions.impexp.enableExportForNonAdminUser = 1"); $I->useExistingSession('editor'); $I->click($selectedPageIcon); $this->selectInContextMenu($I, [$this->contextMenuMore]); $I->waitForElementVisible('#contentMenu1', 5); - $I->seeElement($this->contextMenuExport); $I->seeElement($this->contextMenuImport); + $I->seeElement($this->contextMenuExport); $I->useExistingSession('admin'); }
typo3/sysext/core/Tests/Functional/Authentication/BackendUserAuthenticationTest.php+66 −0 modified@@ -147,4 +147,70 @@ public function mfaRequiredExceptionIsThrown(): void // which should fail since the user in the fixture has MFA activated but not yet passed. $this->setUpBackendUser(4); } + + public function isImportEnabledDataProvider(): array + { + return [ + 'admin user' => [ + 1, + '', + true, + ], + 'editor user' => [ + 2, + '', + false, + ], + 'editor user - enableImportForNonAdminUser = 1' => [ + 2, + 'options.impexp.enableImportForNonAdminUser = 1', + true, + ], + ]; + } + + /** + * @test + * @dataProvider isImportEnabledDataProvider + */ + public function isImportEnabledReturnsExpectedValues(int $userId, string $tsConfig, bool $expected): void + { + $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'] = $tsConfig; + + $subject = $this->setUpBackendUser($userId); + self::assertEquals($expected, $subject->isImportEnabled()); + } + + public function isExportEnabledDataProvider(): array + { + return [ + 'admin user' => [ + 1, + '', + true, + ], + 'editor user' => [ + 2, + '', + false, + ], + 'editor user - enableExportForNonAdminUser = 1' => [ + 2, + 'options.impexp.enableExportForNonAdminUser = 1', + true, + ], + ]; + } + + /** + * @test + * @dataProvider isExportEnabledDataProvider + */ + public function isExportEnabledReturnsExpectedValues(int $userId, string $tsConfig, bool $expected): void + { + $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultUserTSconfig'] = $tsConfig; + + $subject = $this->setUpBackendUser($userId); + self::assertEquals($expected, $subject->isExportEnabled()); + } }
typo3/sysext/impexp/Classes/ContextMenu/ItemProvider.php+2 −11 modified@@ -97,10 +97,10 @@ protected function canRender(string $itemName, string $type): bool $canRender = false; switch ($itemName) { case 'exportT3d': - $canRender = true; + $canRender = $this->backendUser->isExportEnabled(); break; case 'importT3d': - $canRender = $this->table === 'pages' && $this->isImportEnabled(); + $canRender = $this->table === 'pages' && $this->backendUser->isImportEnabled(); break; } return $canRender; @@ -131,13 +131,4 @@ protected function getAdditionalAttributes(string $itemName): array return $attributes; } - - /** - * Check if import functionality is available for current user - */ - protected function isImportEnabled(): bool - { - return $this->backendUser->isAdmin() - || (bool)($this->backendUser->getTSConfig()['options.']['impexp.']['enableImportForNonAdminUser'] ?? false); - } }
typo3/sysext/impexp/Classes/Controller/ExportController.php+8 −0 modified@@ -81,6 +81,14 @@ public function __construct( public function handleRequest(ServerRequestInterface $request): ResponseInterface { + if ($this->getBackendUser()->isExportEnabled() === false) { + throw new \RuntimeException( + 'Export module is disabled for non admin users and ' + . 'userTsConfig options.impexp.enableExportForNonAdminUser is not enabled.', + 1636901978 + ); + } + $backendUser = $this->getBackendUser(); $queryParams = $request->getQueryParams(); $parsedBody = $request->getParsedBody();
typo3/sysext/impexp/Classes/Controller/ImportController.php+1 −10 modified@@ -59,7 +59,7 @@ public function __construct( public function handleRequest(ServerRequestInterface $request): ResponseInterface { - if (!$this->isImportEnabled()) { + if (!$this->getBackendUser()->isImportEnabled()) { throw new \RuntimeException( 'Import module is disabled for non admin users and userTsConfig options.impexp.enableImportForNonAdminUser is not enabled.', 1464435459 @@ -142,15 +142,6 @@ protected function addDocHeaderPreviewButton(ModuleTemplate $view, int $pageUid) $buttonBar->addButton($viewButton); } - /** - * Check if import functionality is available for current user. - */ - protected function isImportEnabled(): bool - { - $backendUser = $this->getBackendUser(); - return $backendUser->isAdmin() || ($backendUser->getTSConfig()['options.']['impexp.']['enableImportForNonAdminUser'] ?? false); - } - protected function handleFileUpload(ServerRequestInterface $request): ?File { $parsedBody = $request->getParsedBody() ?? [];
typo3/sysext/reports/Classes/Report/Status/SecurityStatus.php+46 −0 modified@@ -55,6 +55,7 @@ public function getStatus(ServerRequestInterface $request = null): array 'fileDenyPattern' => $this->getFileDenyPatternStatus(), 'htaccessUpload' => $this->getHtaccessUploadStatus(), 'exceptionHandler' => $this->getExceptionHandlerStatus(), + 'exportedFiles' => $this->getExportedFilesStatus(), ]; if ($request !== null) { @@ -265,6 +266,51 @@ protected function getExceptionHandlerStatus(): ReportStatus return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_exceptionHandler'), $value, $message, $severity); } + protected function getExportedFilesStatus(): ReportStatus + { + $value = $this->getLanguageService()->getLL('status_ok'); + $message = ''; + $severity = ReportStatus::OK; + + $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_file'); + $exportedFiles = $queryBuilder + ->select('storage', 'identifier') + ->from('sys_file') + ->where( + $queryBuilder->expr()->like( + 'identifier', + $queryBuilder->createNamedParameter('%/_temp_/importexport/%') + ), + $queryBuilder->expr()->or( + $queryBuilder->expr()->like( + 'identifier', + $queryBuilder->createNamedParameter('%.xml') + ), + $queryBuilder->expr()->like( + 'identifier', + $queryBuilder->createNamedParameter('%.t3d') + ) + ), + ) + ->executeQuery() + ->fetchAllAssociative(); + + if (count($exportedFiles) > 0) { + $files = []; + foreach ($exportedFiles as $exportedFile) { + $files[] = '<li>' . htmlspecialchars($exportedFile['storage'] . ':' . $exportedFile['identifier']) . '</li>'; + } + + $value = $this->getLanguageService()->getLL('status_insecure'); + $severity = ReportStatus::WARNING; + $message = $this->getLanguageService()->getLL('status_exportedFiles_warningMessage'); + $message .= '<ul>' . implode(PHP_EOL, $files) . '</ul>'; + $message .= $this->getLanguageService()->getLL('status_exportedFiles_warningRecommendation'); + } + + return GeneralUtility::makeInstance(ReportStatus::class, $this->getLanguageService()->getLL('status_exportedFiles'), $value, $message, $severity); + } + protected function getLanguageService(): LanguageService { return $GLOBALS['LANG'];
typo3/sysext/reports/Resources/Private/Language/locallang_reports.xlf+9 −0 modified@@ -156,12 +156,21 @@ <trans-unit id="status_exceptionHandler" resname="status_exceptionHandler"> <source>Exception Handler / Error Reporting</source> </trans-unit> + <trans-unit id="status_exportedFiles" resname="status_exportedFiles"> + <source>XML/T3D export files</source> + </trans-unit> <trans-unit id="status_exceptionHandler_warningMessage" resname="status_exceptionHandler_warningMessage"> <source>Display Errors is set to 1 - errors will be displayed with the DebugExceptionHandler including stack traces.</source> </trans-unit> <trans-unit id="status_exceptionHandler_errorMessage" resname="status_exceptionHandler_errorMessage"> <source>Debug Exception Handler enabled in Production Context - will show full error messages including stack traces.</source> </trans-unit> + <trans-unit id="status_exportedFiles_warningMessage" resname="status_exportedFiles_warningMessage"> + <source>The following exported files where found:</source> + </trans-unit> + <trans-unit id="status_exportedFiles_warningRecommendation" resname="status_exportedFiles_warningRecommendation"> + <source>It is recommended to delete exported files to avoid possible disclosure of exported data to backend users with lower/different access rights than user(s) who originally created the export(s).</source> + </trans-unit> <trans-unit id="status_installTool" resname="status_installTool"> <source>Install Tool</source> </trans-unit>
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-8gmv-9hwg-w89gghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-31046ghsaADVISORY
- github.com/FriendsOfPHP/security-advisories/blob/master/typo3/cms-core/CVE-2022-31046.yamlghsaWEB
- github.com/FriendsOfPHP/security-advisories/blob/master/typo3/cms/CVE-2022-31046.yamlghsaWEB
- github.com/TYPO3/typo3/commit/7447a3d1283017d2ee08737a7972c720001a93e9ghsax_refsource_MISCWEB
- github.com/TYPO3/typo3/security/advisories/GHSA-8gmv-9hwg-w89gghsax_refsource_CONFIRMWEB
- typo3.org/security/advisory/typo3-core-sa-2022-001ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.