Improper Access Control of Resources Referenced by t3:// URI Scheme in TYPO3
Description
TYPO3 is an open source PHP based web content management system released under the GNU GPL. The TYPO3-specific t3:// URI scheme could be used to access resources outside of the users' permission scope. This encompassed files, folders, pages, and records (although only if a valid link-handling configuration was provided). Exploiting this vulnerability requires a valid backend user account. Users are advised to update to TYPO3 versions 8.7.57 ELTS, 9.5.46 ELTS, 10.4.43 ELTS, 11.5.35 LTS, 12.4.11 LTS, 13.0.1 that fix the problem described. There are no known workarounds for this issue.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
TYPO3's t3:// URI scheme allows authenticated backend users to access resources beyond their permissions, patched in multiple versions.
Vulnerability
Overview
The vulnerability in TYPO3, an open-source PHP CMS, stems from the t3:// URI scheme used for internal linking. This scheme could be exploited to access files, folders, pages, and records outside the user's permission scope, provided a valid link-handling configuration exists. The issue affects backend users, requiring authentication to exploit [2].
Exploitation
Details
An authenticated backend user can craft a t3:// URI referencing privileged resources, bypassing access controls. The attack surface is limited to users with backend accounts, but no special privileges are needed beyond that. The t3:// scheme is processed by typolink functions, which may not properly validate permissions for resource references [1]. The exploit does not require any user interaction beyond clicking a crafted link.
Impact
Successful exploitation allows an attacker to read sensitive information from restricted files, folders, pages, or records. This can lead to unauthorized disclosure of confidential data, potentially including system configuration or user information.
Mitigation
The issue is fixed in TYPO3 versions 8.7.57 ELTS, 9.5.46 ELTS, 10.4.43 ELTS, 11.5.35 LTS, 12.4.11 LTS, and 13.0.1 [2]. The commit introduces validation checks, such as ensuring file extensions are checked against both the file name and its identifier [4]. There are no known workarounds, so updating is strongly recommended.
AI Insight generated on May 20, 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 | >= 8.0.0, < 8.7.57 | 8.7.57 |
typo3/cms-corePackagist | >= 9.0.0, < 9.5.46 | 9.5.46 |
typo3/cms-corePackagist | >= 10.0.0, < 10.4.43 | 10.4.43 |
typo3/cms-corePackagist | >= 11.0.0, < 11.5.35 | 11.5.35 |
typo3/cms-corePackagist | >= 12.0.0, < 12.4.11 | 12.4.11 |
typo3/cms-corePackagist | >= 13.0.0, < 13.0.1 | 13.0.1 |
Affected products
2Patches
3ae0dfc4c058a[SECURITY] Prevent arbitrary access to privileged resources via t3://
19 files changed · +110 −36
typo3/sysext/backend/Classes/Backend/Shortcut/ShortcutRepository.php+1 −1 modified@@ -405,7 +405,7 @@ protected function initShortcuts(): array $combinedIdentifier = (string)($arguments['id'] ?? ''); if ($combinedIdentifier !== '') { $storage = GeneralUtility::makeInstance(StorageRepository::class)->findByCombinedIdentifier($combinedIdentifier); - if ($storage === null || $storage->getUid() === 0) { + if ($storage === null || $storage->isFallbackStorage()) { // Continue, if invalid storage or disallowed fallback storage continue; }
typo3/sysext/backend/Classes/Controller/LinkController.php+1 −1 modified@@ -56,7 +56,7 @@ public function resourceAction(ServerRequestInterface $request): ResponseInterfa if (!$resource instanceof File && !$resource instanceof Folder) { throw new \InvalidArgumentException('Resource must be a file or a folder', 1679039649); } - if ($resource->getStorage()->getUid() === 0) { + if ($resource->getStorage()->isFallbackStorage()) { throw new InsufficientFileAccessPermissionsException('You are not allowed to access files outside your storages', 1679039650); } if ($resource instanceof File) {
typo3/sysext/backend/Classes/Controller/Resource/ResourceController.php+1 −1 modified@@ -54,7 +54,7 @@ public function renameResourceAction(ServerRequestInterface $request): ResponseI if (!$origin instanceof File && !$origin instanceof Folder) { throw new \InvalidArgumentException('Resource must be a file or a folder', 1676979120); } - if ($origin->getStorage()->getUid() === 0) { + if ($origin->getStorage()->isFallbackStorage()) { throw new InsufficientFileAccessPermissionsException('You are not allowed to access files outside your storages', 1676299579); } if (!$origin->checkActionPermission('rename')) {
typo3/sysext/backend/Classes/Form/Element/LinkElement.php+9 −4 modified@@ -32,6 +32,7 @@ use TYPO3\CMS\Core\Resource\Exception\InvalidPathException; use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\Folder; +use TYPO3\CMS\Core\Type\Bitmask\Permission; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Core\Utility\StringUtility; @@ -339,10 +340,12 @@ protected function getLinkExplanation(string $itemValue): array } } + $backendUser = $this->getBackendUser(); // Resolve the actual link switch ($linkData['type']) { case LinkService::TYPE_PAGE: - $pageRecord = BackendUtility::readPageAccess($linkData['pageuid'] ?? null, '1=1'); + $pagePermissionClause = $backendUser->getPagePermsClause(Permission::PAGE_SHOW); + $pageRecord = BackendUtility::readPageAccess($linkData['pageuid'] ?? null, $pagePermissionClause); // Is this a real page if ($pageRecord['uid'] ?? 0) { $fragmentTitle = ''; @@ -376,7 +379,7 @@ protected function getLinkExplanation(string $itemValue): array break; case LinkService::TYPE_FILE: $file = $linkData['file'] ?? null; - if ($file instanceof File) { + if ($file instanceof File && $file->checkActionPermission('read') && !$file->getStorage()->isFallbackStorage()) { $data = [ 'text' => $file->getPublicUrl(), 'icon' => $this->iconFactory->getIconForFileExtension($file->getExtension(), IconSize::SMALL)->render(), @@ -385,7 +388,7 @@ protected function getLinkExplanation(string $itemValue): array break; case LinkService::TYPE_FOLDER: $folder = $linkData['folder'] ?? null; - if ($folder instanceof Folder) { + if ($folder instanceof Folder && $folder->checkActionPermission('read') && !$folder->getStorage()->isFallbackStorage()) { $data = [ 'text' => $folder->getPublicUrl(), 'icon' => $this->iconFactory->getIcon('apps-filetree-folder-default', IconSize::SMALL)->render(), @@ -395,7 +398,9 @@ protected function getLinkExplanation(string $itemValue): array case LinkService::TYPE_RECORD: $table = $this->data['pageTsConfig']['TCEMAIN.']['linkHandler.'][$linkData['identifier'] . '.']['configuration.']['table'] ?? ''; $record = BackendUtility::getRecord($table, $linkData['uid']); - if ($record) { + $pagePermissionClause = $backendUser->getPagePermsClause(Permission::PAGE_SHOW); + $hasPageAccess = BackendUtility::readPageAccess($record['pid'] ?? null, $pagePermissionClause) !== false; + if ($record && $hasPageAccess && $backendUser->check('tables_select', $table)) { $recordTitle = BackendUtility::getRecordTitle($table, $record); $tableTitle = $this->getLanguageService()->sL($GLOBALS['TCA'][$table]['ctrl']['title']); $data = [
typo3/sysext/backend/Classes/LinkHandler/PageLinkHandler.php+11 −2 modified@@ -27,6 +27,7 @@ use TYPO3\CMS\Core\Domain\Repository\PageRepository; use TYPO3\CMS\Core\Imaging\IconSize; use TYPO3\CMS\Core\LinkHandling\LinkService; +use TYPO3\CMS\Core\Type\Bitmask\Permission; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; @@ -89,11 +90,19 @@ public function formatCurrentUrl() $titleLen = (int)$this->getBackendUser()->uc['titleLen']; $id = (int)$this->linkParts['url']['pageuid']; - $pageTitle = BackendUtility::getRecordWSOL('pages', $id, 'title')['title'] ?? ''; + $idInfo = 'ID: ' . $id . (!empty($this->linkParts['url']['fragment']) ? ', #' . $this->linkParts['url']['fragment'] : ''); + + $permsClause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW); + $pageRecord = BackendUtility::readPageAccess($id, $permsClause); + if ($pageRecord === false) { + return $lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_browse_links.xlf:page') . ' ' . $idInfo; + } + + $pageTitle = $pageRecord['title'] ?? ''; return $lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_browse_links.xlf:page') . ($pageTitle ? ' \'' . GeneralUtility::fixed_lgd_cs($pageTitle, $titleLen) . '\'' : '') - . ' (ID: ' . $id . (!empty($this->linkParts['url']['fragment']) ? ', #' . $this->linkParts['url']['fragment'] : '') . ')'; + . ' (' . $idInfo . ')'; } /**
typo3/sysext/core/Classes/LinkHandling/FileLinkHandler.php+8 −0 modified@@ -20,6 +20,7 @@ use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException; use TYPO3\CMS\Core\Resource\FileInterface; use TYPO3\CMS\Core\Resource\ResourceFactory; +use TYPO3\CMS\Core\Resource\Security\FileNameValidator; use TYPO3\CMS\Core\Utility\GeneralUtility; /** @@ -71,6 +72,13 @@ public function resolveHandlerData(array $data): array { try { $file = $this->resolveFile($data); + $fileNameValidator = GeneralUtility::makeInstance(FileNameValidator::class); + if ( + !$fileNameValidator->isValid(basename($file->getIdentifier())) || + !$fileNameValidator->isValid($file->getName()) + ) { + $file = null; + } } catch (FileDoesNotExistException $e) { $file = null; }
typo3/sysext/core/Classes/LinkHandling/LegacyLinkNotationConverter.php+1 −1 modified@@ -220,7 +220,7 @@ protected function getFileOrFolderObjectFromMixedIdentifier(string $mixedIdentif } $fileOrFolderObject = $this->getResourceFactory()->retrieveFileOrFolderObject($fileIdentifier); // Links to a file/folder in the main TYPO3 directory should not be considered as file links, but an external link - if ($fileOrFolderObject instanceof ResourceInterface && $fileOrFolderObject->getStorage()->getUid() === 0) { + if ($fileOrFolderObject instanceof ResourceInterface && $fileOrFolderObject->getStorage()->isFallbackStorage()) { return [ 'type' => LinkService::TYPE_URL, 'url' => $mixedIdentifier,
typo3/sysext/core/Classes/Resource/ResourceStorage.php+29 −6 modified@@ -362,6 +362,17 @@ public function hasChildren() return true; } + /** + * Returns true if this storage is a virtual storage that provides + * access to all files in the project root. + * + * @internal + */ + public function isFallbackStorage(): bool + { + return $this->getUid() === 0; + } + /********************************* * Capabilities ********************************/ @@ -719,7 +730,7 @@ public function checkFileActionPermission($action, FileInterface $file) return false; } // Check 3: No action allowed on files for denied file extensions - if (!$this->checkFileExtensionPermission($file->getName())) { + if (!$this->checkValidFileExtension($file)) { return false; } $isReadCheck = false; @@ -831,6 +842,17 @@ protected function checkFileExtensionPermission($fileName) return GeneralUtility::makeInstance(FileNameValidator::class)->isValid($fileName); } + /** + * Check file extension of an existing file against the + * current file deny pattern. + */ + protected function checkValidFileExtension(FileInterface $file): bool + { + $fileNameValidator = GeneralUtility::makeInstance(FileNameValidator::class); + return $fileNameValidator->isValid($file->getName()) && + $fileNameValidator->isValid(basename($file->getIdentifier())); + } + /** * Assures read permission for given folder. * @@ -897,7 +919,7 @@ protected function assureFileReadPermission(FileInterface $file) 1375955429 ); } - if (!$this->checkFileExtensionPermission($file->getName())) { + if (!$this->checkValidFileExtension($file)) { throw new IllegalFileExtensionException( 'You are not allowed to use that file extension. File: "' . $file->getName() . '"', 1375955430 @@ -918,7 +940,7 @@ protected function assureFileWritePermissions(FileInterface $file) if (!$this->checkFileActionPermission('write', $file)) { throw new InsufficientFileWritePermissionsException('Writing to file "' . $file->getIdentifier() . '" is not allowed.', 1330121088); } - if (!$this->checkFileExtensionPermission($file->getName())) { + if (!$this->checkValidFileExtension($file)) { throw new IllegalFileExtensionException('You are not allowed to edit a file with extension "' . $file->getExtension() . '"', 1366711933); } } @@ -951,7 +973,7 @@ protected function assureFileReplacePermissions(FileInterface $file) protected function assureFileDeletePermissions(FileInterface $file) { // Check for disallowed file extensions - if (!$this->checkFileExtensionPermission($file->getName())) { + if (!$this->checkValidFileExtension($file)) { throw new IllegalFileExtensionException('You are not allowed to delete a file with extension "' . $file->getExtension() . '"', 1377778916); } // Check further permissions if file is not a processed file @@ -1072,7 +1094,7 @@ protected function assureFileMovePermissions(FileInterface $file, Folder $target protected function assureFileRenamePermissions(FileInterface $file, $targetFileName) { // Check if file extension is allowed - if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkFileExtensionPermission($file->getName())) { + if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkValidFileExtension($file)) { throw new IllegalFileExtensionException('You are not allowed to rename a file with this extension. File given: "' . $file->getName() . '"', 1371466663); } // Check if user is allowed to rename @@ -1115,7 +1137,7 @@ protected function assureFileCopyPermissions(FileInterface $file, Folder $target throw new InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1319550435); } // Check for a valid file extension - if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkFileExtensionPermission($file->getName())) { + if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkValidFileExtension($file)) { throw new IllegalFileExtensionException('You are not allowed to copy a file of that type.', 1319553317); } } @@ -1736,6 +1758,7 @@ public function streamFile( string $alternativeFilename = null, string $overrideMimeType = null ): ResponseInterface { + $this->assureFileReadPermission($file); if (!$this->driver instanceof StreamableDriverInterface) { return $this->getPseudoStream($file, $asDownload, $alternativeFilename, $overrideMimeType); }
typo3/sysext/core/Classes/Resource/Security/StoragePermissionsAspect.php+2 −5 modified@@ -47,13 +47,10 @@ public function addUserPermissionsToStorage(AfterResourceStorageInitializationEv if (($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface && ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend() && !$this->getBackendUser()->isAdmin() + && !$storage->isFallbackStorage() ) { $storage->setEvaluatePermissions(true); - if ($storage->getUid() > 0) { - $storage->setUserPermissions($this->getFilePermissionsForStorage($storage)); - } else { - $storage->setEvaluatePermissions(false); - } + $storage->setUserPermissions($this->getFilePermissionsForStorage($storage)); $this->addFileMountsToStorage($storage); } }
typo3/sysext/core/Classes/Utility/File/ExtendedFileUtility.php+1 −1 modified@@ -572,7 +572,7 @@ protected function getFileObject($identifier) if ($object === null) { throw new InvalidFileException('The item ' . $identifier . ' was not a file or directory', 1320122453); } - if ($object->getStorage()->getUid() === 0) { + if ($object->getStorage()->isFallbackStorage()) { throw new InsufficientFileAccessPermissionsException('You are not allowed to access files outside your storages', 1375889830); } return $object;
typo3/sysext/core/Tests/Unit/DataHandling/SoftReference/TypoLinkSoftReferenceParserTest.php+3 −0 modified@@ -260,6 +260,9 @@ public function findRefReturnsParsedElementsWithFile(array $softrefConfiguration $storageObject->method('getUid')->willReturn(1); $fileObject = $this->createMock(File::class); $fileObject->expects(self::once())->method('getUid')->willReturn(42); + $fileObject->expects(self::any())->method('getName')->willReturn('download.jpg'); + $fileObject->expects(self::any())->method('getIdentifier')->willReturn('fileadmin/download.jpg'); + $fileObject->expects(self::any())->method('getStorage')->willReturn($storageObject); $resourceFactory = $this->createMock(ResourceFactory::class);
typo3/sysext/core/Tests/Unit/DataHandling/SoftReference/TypoLinkTagSoftReferenceParserTest.php+2 −0 modified@@ -200,6 +200,8 @@ public function findRefReturnsParsedElementsWithFile(array $softrefConfiguration { $fileObject = $this->createMock(File::class); $fileObject->expects(self::once())->method('getUid')->willReturn(42); + $fileObject->expects(self::any())->method('getName')->willReturn('download.jpg'); + $fileObject->expects(self::any())->method('getIdentifier')->willReturn('fileadmin/download.jpg'); $resourceFactory = $this->createMock(ResourceFactory::class); $resourceFactory->method('getFileObject')->with('42')->willReturn($fileObject);
typo3/sysext/core/Tests/Unit/LinkHandling/FileLinkHandlerTest.php+1 −1 modified@@ -98,7 +98,7 @@ public function resolveFileReferencesToSplitParameters(array $input, array $expe ->getMock(); // fake methods to return proper objects - $fileObject = new File(['identifier' => $expected['file'], 'name' => 'foobar.txt'], $storage); + $fileObject = new File(['identifier' => 'fileadmin/deep/down.jpg', 'name' => 'down.jpg'], $storage); $factory->method('getFileObject')->with($expected['file'])->willReturn($fileObject); $factory->method('getFileObjectFromCombinedIdentifier')->with($expected['file'])->willReturn($fileObject); $expected['file'] = $fileObject;
typo3/sysext/filelist/Classes/Controller/File/CreateFileController.php+1 −1 modified@@ -121,7 +121,7 @@ protected function initialize(ServerRequestInterface $request): void $message = $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:targetNoDir'); throw new \RuntimeException($title . ': ' . $message, 1667565756); } - if ($this->folderObject->getStorage()->getUid() === 0) { + if ($this->folderObject->getStorage()->isFallbackStorage()) { throw new InsufficientFolderAccessPermissionsException( 'You are not allowed to access folders outside your storages', 1667565757
typo3/sysext/filelist/Classes/Controller/File/EditFileController.php+1 −1 modified@@ -121,7 +121,7 @@ public function mainAction(ServerRequestInterface $request): ResponseInterface if (!$file instanceof FileInterface) { throw new InvalidFileException('Referenced target "' . $combinedIdentifier . '" could not be resolved to a valid file', 1294586841); } - if ($file->getStorage()->getUid() === 0) { + if ($file->getStorage()->isFallbackStorage()) { throw new InsufficientFileAccessPermissionsException('You are not allowed to access files outside your storages', 1375889832); }
typo3/sysext/filelist/Classes/Controller/File/FileUploadController.php+1 −1 modified@@ -60,7 +60,7 @@ public function mainAction(ServerRequestInterface $request): ResponseInterface $folder = $this->resourceFactory->retrieveFileOrFolderObject($targetFolderCombinedIdentifier); if (!$folder instanceof FolderInterface - || $folder->getStorage()->getUid() === 0 + || $folder->getStorage()->isFallbackStorage() ) { throw new InsufficientFolderAccessPermissionsException('You are not allowed to access folders outside your storages, or the folder couldn\'t be resolved', 1375889834); }
typo3/sysext/filelist/Classes/Controller/FileListController.php+1 −1 modified@@ -126,7 +126,7 @@ public function handleRequest(ServerRequestInterface $request): ResponseInterfac } $this->folderObject = $storage->getFolder($identifier); // Disallow access to fallback storage 0 - if ($storage->getUid() === 0) { + if ($storage->isFallbackStorage()) { throw new InsufficientFolderAccessPermissionsException( 'You are not allowed to access files outside your storages', 1434539815
typo3/sysext/filelist/Classes/Controller/File/ReplaceFileController.php+1 −1 modified@@ -106,7 +106,7 @@ protected function init(ServerRequestInterface $request): void $message = $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:targetNoDir'); throw new \RuntimeException($title . ': ' . $message, 1436895930); } - if ($this->fileOrFolderObject->getStorage()->getUid() === 0) { + if ($this->fileOrFolderObject->getStorage()->isFallbackStorage()) { throw new InsufficientFileAccessPermissionsException( 'You are not allowed to access files outside your storages', 1436895931
typo3/sysext/filelist/Classes/LinkHandler/AbstractResourceLinkHandler.php+35 −8 modified@@ -94,6 +94,13 @@ public function canHandleLink(array $linkParts): bool public function formatCurrentUrl(): string { + $resource = $this->linkParts['url'][$this->type->value]; + if (!$resource->checkActionPermission('read')) { + return ''; + } + if ($resource->getStorage()->isFallbackStorage()) { + return ''; + } return $this->linkParts['url'][$this->type->value]->getName(); } @@ -178,6 +185,12 @@ public function initializeVariables(ServerRequestInterface $request): void } catch (FolderDoesNotExistException $e) { } } + if ($this->selectedFolder?->checkActionPermission('read') === false) { + $this->selectedFolder = null; + } + if ($this->selectedFolder?->getStorage()?->isFallbackStorage()) { + $this->selectedFolder = null; + } if (!$this->selectedFolder) { $this->selectedFolder = $this->resourceFactory->getDefaultStorage()?->getRootLevelFolder() ?? null; } @@ -194,6 +207,13 @@ public function modifyLinkAttributes(array $fieldDefinitions): array public function isUpdateSupported(): bool { + $resource = $this->linkParts['url'][$this->type->value]; + if (!$resource->checkActionPermission('read')) { + return false; + } + if ($resource->getStorage()->isFallbackStorage()) { + return false; + } return true; } @@ -202,15 +222,22 @@ public function isUpdateSupported(): bool */ public function getBodyTagAttributes(): array { - if (isset($this->linkParts['url'][$this->type->value]) && $this->linkParts['url'][$this->type->value] instanceof ($this->type->getResourceType())) { - return [ - 'data-linkbrowser-current-link' => GeneralUtility::makeInstance(LinkService::class)->asString([ - 'type' => $this->type->getLinkServiceType(), - $this->type->value => $this->linkParts['url'][$this->type->value], - ]), - ]; + $resource = $this->linkParts['url'][$this->type->value] ?? null; + if (!$resource instanceof ($this->type->getResourceType())) { + return []; + } + if (!$resource->checkActionPermission('read')) { + return []; + } + if ($resource->getStorage()->isFallbackStorage()) { + return []; } - return []; + return [ + 'data-linkbrowser-current-link' => GeneralUtility::makeInstance(LinkService::class)->asString([ + 'type' => $this->type->getLinkServiceType(), + $this->type->value => $resource, + ]), + ]; } protected function createUri(ServerRequestInterface $request, array $parameters = []): string
33f4d279b82b[SECURITY] Prevent arbitrary access to privileged resources via t3://
19 files changed · +110 −36
typo3/sysext/backend/Classes/Backend/Shortcut/ShortcutRepository.php+1 −1 modified@@ -405,7 +405,7 @@ protected function initShortcuts(): array $combinedIdentifier = (string)($arguments['id'] ?? ''); if ($combinedIdentifier !== '') { $storage = GeneralUtility::makeInstance(StorageRepository::class)->findByCombinedIdentifier($combinedIdentifier); - if ($storage === null || $storage->getUid() === 0) { + if ($storage === null || $storage->isFallbackStorage()) { // Continue, if invalid storage or disallowed fallback storage continue; }
typo3/sysext/backend/Classes/Controller/LinkController.php+1 −1 modified@@ -56,7 +56,7 @@ public function resourceAction(ServerRequestInterface $request): ResponseInterfa if (!$resource instanceof File && !$resource instanceof Folder) { throw new \InvalidArgumentException('Resource must be a file or a folder', 1679039649); } - if ($resource->getStorage()->getUid() === 0) { + if ($resource->getStorage()->isFallbackStorage()) { throw new InsufficientFileAccessPermissionsException('You are not allowed to access files outside your storages', 1679039650); } if ($resource instanceof File) {
typo3/sysext/backend/Classes/Controller/Resource/ResourceController.php+1 −1 modified@@ -54,7 +54,7 @@ public function renameResourceAction(ServerRequestInterface $request): ResponseI if (!$origin instanceof File && !$origin instanceof Folder) { throw new \InvalidArgumentException('Resource must be a file or a folder', 1676979120); } - if ($origin->getStorage()->getUid() === 0) { + if ($origin->getStorage()->isFallbackStorage()) { throw new InsufficientFileAccessPermissionsException('You are not allowed to access files outside your storages', 1676299579); } if (!$origin->checkActionPermission('rename')) {
typo3/sysext/backend/Classes/Form/Element/LinkElement.php+9 −4 modified@@ -31,6 +31,7 @@ use TYPO3\CMS\Core\Resource\Exception\InvalidPathException; use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\Folder; +use TYPO3\CMS\Core\Type\Bitmask\Permission; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Core\Utility\StringUtility; @@ -335,10 +336,12 @@ protected function getLinkExplanation(string $itemValue): array } } + $backendUser = $this->getBackendUser(); // Resolve the actual link switch ($linkData['type']) { case LinkService::TYPE_PAGE: - $pageRecord = BackendUtility::readPageAccess($linkData['pageuid'] ?? null, '1=1'); + $pagePermissionClause = $backendUser->getPagePermsClause(Permission::PAGE_SHOW); + $pageRecord = BackendUtility::readPageAccess($linkData['pageuid'] ?? null, $pagePermissionClause); // Is this a real page if ($pageRecord['uid'] ?? 0) { $fragmentTitle = ''; @@ -372,7 +375,7 @@ protected function getLinkExplanation(string $itemValue): array break; case LinkService::TYPE_FILE: $file = $linkData['file'] ?? null; - if ($file instanceof File) { + if ($file instanceof File && $file->checkActionPermission('read') && !$file->getStorage()->isFallbackStorage()) { $data = [ 'text' => $file->getPublicUrl(), 'icon' => $this->iconFactory->getIconForFileExtension($file->getExtension(), Icon::SIZE_SMALL)->render(), @@ -381,7 +384,7 @@ protected function getLinkExplanation(string $itemValue): array break; case LinkService::TYPE_FOLDER: $folder = $linkData['folder'] ?? null; - if ($folder instanceof Folder) { + if ($folder instanceof Folder && $folder->checkActionPermission('read') && !$folder->getStorage()->isFallbackStorage()) { $data = [ 'text' => $folder->getPublicUrl(), 'icon' => $this->iconFactory->getIcon('apps-filetree-folder-default', Icon::SIZE_SMALL)->render(), @@ -391,7 +394,9 @@ protected function getLinkExplanation(string $itemValue): array case LinkService::TYPE_RECORD: $table = $this->data['pageTsConfig']['TCEMAIN.']['linkHandler.'][$linkData['identifier'] . '.']['configuration.']['table'] ?? ''; $record = BackendUtility::getRecord($table, $linkData['uid']); - if ($record) { + $pagePermissionClause = $backendUser->getPagePermsClause(Permission::PAGE_SHOW); + $hasPageAccess = BackendUtility::readPageAccess($record['pid'] ?? null, $pagePermissionClause) !== false; + if ($record && $hasPageAccess && $backendUser->check('tables_select', $table)) { $recordTitle = BackendUtility::getRecordTitle($table, $record); $tableTitle = $this->getLanguageService()->sL($GLOBALS['TCA'][$table]['ctrl']['title']); $data = [
typo3/sysext/backend/Classes/LinkHandler/PageLinkHandler.php+11 −2 modified@@ -25,6 +25,7 @@ use TYPO3\CMS\Core\Domain\Repository\PageRepository; use TYPO3\CMS\Core\Imaging\Icon; use TYPO3\CMS\Core\LinkHandling\LinkService; +use TYPO3\CMS\Core\Type\Bitmask\Permission; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; @@ -87,11 +88,19 @@ public function formatCurrentUrl() $titleLen = (int)$this->getBackendUser()->uc['titleLen']; $id = (int)$this->linkParts['url']['pageuid']; - $pageTitle = BackendUtility::getRecordWSOL('pages', $id, 'title')['title'] ?? ''; + $idInfo = 'ID: ' . $id . (!empty($this->linkParts['url']['fragment']) ? ', #' . $this->linkParts['url']['fragment'] : ''); + + $permsClause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW); + $pageRecord = BackendUtility::readPageAccess($id, $permsClause); + if ($pageRecord === false) { + return $lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_browse_links.xlf:page') . ' ' . $idInfo; + } + + $pageTitle = $pageRecord['title'] ?? ''; return $lang->sL('LLL:EXT:backend/Resources/Private/Language/locallang_browse_links.xlf:page') . ($pageTitle ? ' \'' . GeneralUtility::fixed_lgd_cs($pageTitle, $titleLen) . '\'' : '') - . ' (ID: ' . $id . (!empty($this->linkParts['url']['fragment']) ? ', #' . $this->linkParts['url']['fragment'] : '') . ')'; + . ' (' . $idInfo . ')'; } /**
typo3/sysext/core/Classes/LinkHandling/FileLinkHandler.php+8 −0 modified@@ -20,6 +20,7 @@ use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException; use TYPO3\CMS\Core\Resource\FileInterface; use TYPO3\CMS\Core\Resource\ResourceFactory; +use TYPO3\CMS\Core\Resource\Security\FileNameValidator; use TYPO3\CMS\Core\Utility\GeneralUtility; /** @@ -71,6 +72,13 @@ public function resolveHandlerData(array $data): array { try { $file = $this->resolveFile($data); + $fileNameValidator = GeneralUtility::makeInstance(FileNameValidator::class); + if ( + !$fileNameValidator->isValid(basename($file->getIdentifier())) || + !$fileNameValidator->isValid($file->getName()) + ) { + $file = null; + } } catch (FileDoesNotExistException $e) { $file = null; }
typo3/sysext/core/Classes/LinkHandling/LegacyLinkNotationConverter.php+1 −1 modified@@ -220,7 +220,7 @@ protected function getFileOrFolderObjectFromMixedIdentifier(string $mixedIdentif } $fileOrFolderObject = $this->getResourceFactory()->retrieveFileOrFolderObject($fileIdentifier); // Links to a file/folder in the main TYPO3 directory should not be considered as file links, but an external link - if ($fileOrFolderObject instanceof ResourceInterface && $fileOrFolderObject->getStorage()->getUid() === 0) { + if ($fileOrFolderObject instanceof ResourceInterface && $fileOrFolderObject->getStorage()->isFallbackStorage()) { return [ 'type' => LinkService::TYPE_URL, 'url' => $mixedIdentifier,
typo3/sysext/core/Classes/Resource/ResourceStorage.php+29 −6 modified@@ -354,6 +354,17 @@ public function hasChildren() return true; } + /** + * Returns true if this storage is a virtual storage that provides + * access to all files in the project root. + * + * @internal + */ + public function isFallbackStorage(): bool + { + return $this->getUid() === 0; + } + /********************************* * Capabilities ********************************/ @@ -718,7 +729,7 @@ public function checkFileActionPermission($action, FileInterface $file) return false; } // Check 3: No action allowed on files for denied file extensions - if (!$this->checkFileExtensionPermission($file->getName())) { + if (!$this->checkValidFileExtension($file)) { return false; } $isReadCheck = false; @@ -830,6 +841,17 @@ protected function checkFileExtensionPermission($fileName) return GeneralUtility::makeInstance(FileNameValidator::class)->isValid($fileName); } + /** + * Check file extension of an existing file against the + * current file deny pattern. + */ + protected function checkValidFileExtension(FileInterface $file): bool + { + $fileNameValidator = GeneralUtility::makeInstance(FileNameValidator::class); + return $fileNameValidator->isValid($file->getName()) && + $fileNameValidator->isValid(basename($file->getIdentifier())); + } + /** * Assures read permission for given folder. * @@ -896,7 +918,7 @@ protected function assureFileReadPermission(FileInterface $file) 1375955429 ); } - if (!$this->checkFileExtensionPermission($file->getName())) { + if (!$this->checkValidFileExtension($file)) { throw new IllegalFileExtensionException( 'You are not allowed to use that file extension. File: "' . $file->getName() . '"', 1375955430 @@ -917,7 +939,7 @@ protected function assureFileWritePermissions(FileInterface $file) if (!$this->checkFileActionPermission('write', $file)) { throw new InsufficientFileWritePermissionsException('Writing to file "' . $file->getIdentifier() . '" is not allowed.', 1330121088); } - if (!$this->checkFileExtensionPermission($file->getName())) { + if (!$this->checkValidFileExtension($file)) { throw new IllegalFileExtensionException('You are not allowed to edit a file with extension "' . $file->getExtension() . '"', 1366711933); } } @@ -950,7 +972,7 @@ protected function assureFileReplacePermissions(FileInterface $file) protected function assureFileDeletePermissions(FileInterface $file) { // Check for disallowed file extensions - if (!$this->checkFileExtensionPermission($file->getName())) { + if (!$this->checkValidFileExtension($file)) { throw new IllegalFileExtensionException('You are not allowed to delete a file with extension "' . $file->getExtension() . '"', 1377778916); } // Check further permissions if file is not a processed file @@ -1071,7 +1093,7 @@ protected function assureFileMovePermissions(FileInterface $file, Folder $target protected function assureFileRenamePermissions(FileInterface $file, $targetFileName) { // Check if file extension is allowed - if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkFileExtensionPermission($file->getName())) { + if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkValidFileExtension($file)) { throw new IllegalFileExtensionException('You are not allowed to rename a file with this extension. File given: "' . $file->getName() . '"', 1371466663); } // Check if user is allowed to rename @@ -1114,7 +1136,7 @@ protected function assureFileCopyPermissions(FileInterface $file, Folder $target throw new InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1319550435); } // Check for a valid file extension - if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkFileExtensionPermission($file->getName())) { + if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkValidFileExtension($file)) { throw new IllegalFileExtensionException('You are not allowed to copy a file of that type.', 1319553317); } } @@ -1733,6 +1755,7 @@ public function streamFile( string $alternativeFilename = null, string $overrideMimeType = null ): ResponseInterface { + $this->assureFileReadPermission($file); if (!$this->driver instanceof StreamableDriverInterface) { return $this->getPseudoStream($file, $asDownload, $alternativeFilename, $overrideMimeType); }
typo3/sysext/core/Classes/Resource/Security/StoragePermissionsAspect.php+2 −5 modified@@ -42,13 +42,10 @@ public function addUserPermissionsToStorage(AfterResourceStorageInitializationEv if (($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface && ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend() && !$GLOBALS['BE_USER']->isAdmin() + && !$storage->isFallbackStorage() ) { $storage->setEvaluatePermissions(true); - if ($storage->getUid() > 0) { - $storage->setUserPermissions($GLOBALS['BE_USER']->getFilePermissionsForStorage($storage)); - } else { - $storage->setEvaluatePermissions(false); - } + $storage->setUserPermissions($GLOBALS['BE_USER']->getFilePermissionsForStorage($storage)); $this->addFileMountsToStorage($storage); } }
typo3/sysext/core/Classes/Utility/File/ExtendedFileUtility.php+1 −1 modified@@ -590,7 +590,7 @@ protected function getFileObject($identifier) if ($object === null) { throw new InvalidFileException('The item ' . $identifier . ' was not a file or directory', 1320122453); } - if ($object->getStorage()->getUid() === 0) { + if ($object->getStorage()->isFallbackStorage()) { throw new InsufficientFileAccessPermissionsException('You are not allowed to access files outside your storages', 1375889830); } return $object;
typo3/sysext/core/Tests/Unit/DataHandling/SoftReference/TypoLinkSoftReferenceParserTest.php+3 −0 modified@@ -247,6 +247,9 @@ public function findRefReturnsParsedElementsWithFile(array $softrefConfiguration $storageObject->method('getUid')->willReturn(1); $fileObject = $this->createMock(File::class); $fileObject->expects(self::once())->method('getUid')->willReturn(42); + $fileObject->expects(self::any())->method('getName')->willReturn('download.jpg'); + $fileObject->expects(self::any())->method('getIdentifier')->willReturn('fileadmin/download.jpg'); + $fileObject->expects(self::any())->method('getStorage')->willReturn($storageObject); $resourceFactory = $this->createMock(ResourceFactory::class);
typo3/sysext/core/Tests/Unit/DataHandling/SoftReference/TypoLinkTagSoftReferenceParserTest.php+2 −0 modified@@ -189,6 +189,8 @@ public function findRefReturnsParsedElementsWithFile(array $softrefConfiguration { $fileObject = $this->createMock(File::class); $fileObject->expects(self::once())->method('getUid')->willReturn(42); + $fileObject->expects(self::any())->method('getName')->willReturn('download.jpg'); + $fileObject->expects(self::any())->method('getIdentifier')->willReturn('fileadmin/download.jpg'); $resourceFactory = $this->createMock(ResourceFactory::class); $resourceFactory->method('getFileObject')->with('42')->willReturn($fileObject);
typo3/sysext/core/Tests/Unit/LinkHandling/FileLinkHandlerTest.php+1 −1 modified@@ -98,7 +98,7 @@ public function resolveFileReferencesToSplitParameters(array $input, array $expe ->getMock(); // fake methods to return proper objects - $fileObject = new File(['identifier' => $expected['file'], 'name' => 'foobar.txt'], $storage); + $fileObject = new File(['identifier' => 'fileadmin/deep/down.jpg', 'name' => 'down.jpg'], $storage); $factory->method('getFileObject')->with($expected['file'])->willReturn($fileObject); $factory->method('getFileObjectFromCombinedIdentifier')->with($expected['file'])->willReturn($fileObject); $expected['file'] = $fileObject;
typo3/sysext/filelist/Classes/Controller/File/CreateFileController.php+1 −1 modified@@ -119,7 +119,7 @@ protected function initialize(ServerRequestInterface $request): void $message = $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:targetNoDir'); throw new \RuntimeException($title . ': ' . $message, 1667565756); } - if ($this->folderObject->getStorage()->getUid() === 0) { + if ($this->folderObject->getStorage()->isFallbackStorage()) { throw new InsufficientFolderAccessPermissionsException( 'You are not allowed to access folders outside your storages', 1667565757
typo3/sysext/filelist/Classes/Controller/File/EditFileController.php+1 −1 modified@@ -119,7 +119,7 @@ public function mainAction(ServerRequestInterface $request): ResponseInterface if (!$file instanceof FileInterface) { throw new InvalidFileException('Referenced target "' . $combinedIdentifier . '" could not be resolved to a valid file', 1294586841); } - if ($file->getStorage()->getUid() === 0) { + if ($file->getStorage()->isFallbackStorage()) { throw new InsufficientFileAccessPermissionsException('You are not allowed to access files outside your storages', 1375889832); }
typo3/sysext/filelist/Classes/Controller/File/FileUploadController.php+1 −1 modified@@ -58,7 +58,7 @@ public function mainAction(ServerRequestInterface $request): ResponseInterface $folder = $this->resourceFactory->retrieveFileOrFolderObject($targetFolderCombinedIdentifier); if (!$folder instanceof FolderInterface - || $folder->getStorage()->getUid() === 0 + || $folder->getStorage()->isFallbackStorage() ) { throw new InsufficientFolderAccessPermissionsException('You are not allowed to access folders outside your storages, or the folder couldn\'t be resolved', 1375889834); }
typo3/sysext/filelist/Classes/Controller/FileListController.php+1 −1 modified@@ -124,7 +124,7 @@ public function handleRequest(ServerRequestInterface $request): ResponseInterfac } $this->folderObject = $storage->getFolder($identifier); // Disallow access to fallback storage 0 - if ($storage->getUid() === 0) { + if ($storage->isFallbackStorage()) { throw new InsufficientFolderAccessPermissionsException( 'You are not allowed to access files outside your storages', 1434539815
typo3/sysext/filelist/Classes/Controller/File/ReplaceFileController.php+1 −1 modified@@ -103,7 +103,7 @@ protected function init(ServerRequestInterface $request): void $message = $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:targetNoDir'); throw new \RuntimeException($title . ': ' . $message, 1436895930); } - if ($this->fileOrFolderObject->getStorage()->getUid() === 0) { + if ($this->fileOrFolderObject->getStorage()->isFallbackStorage()) { throw new InsufficientFileAccessPermissionsException( 'You are not allowed to access files outside your storages', 1436895931
typo3/sysext/filelist/Classes/LinkHandler/AbstractResourceLinkHandler.php+35 −8 modified@@ -98,6 +98,13 @@ public function canHandleLink(array $linkParts): bool public function formatCurrentUrl(): string { + $resource = $this->linkParts['url'][$this->type->value]; + if (!$resource->checkActionPermission('read')) { + return ''; + } + if ($resource->getStorage()->isFallbackStorage()) { + return ''; + } return $this->linkParts['url'][$this->type->value]->getName(); } @@ -181,6 +188,12 @@ public function initializeVariables(ServerRequestInterface $request): void } catch (FolderDoesNotExistException $e) { } } + if ($this->selectedFolder?->checkActionPermission('read') === false) { + $this->selectedFolder = null; + } + if ($this->selectedFolder?->getStorage()?->isFallbackStorage()) { + $this->selectedFolder = null; + } if (!$this->selectedFolder) { $this->selectedFolder = $this->resourceFactory->getDefaultStorage()?->getRootLevelFolder() ?? null; } @@ -202,6 +215,13 @@ public function modifyLinkAttributes(array $fieldDefinitions): array public function isUpdateSupported(): bool { + $resource = $this->linkParts['url'][$this->type->value]; + if (!$resource->checkActionPermission('read')) { + return false; + } + if ($resource->getStorage()->isFallbackStorage()) { + return false; + } return true; } @@ -215,15 +235,22 @@ public function getScriptUrl(): string */ public function getBodyTagAttributes(): array { - if (isset($this->linkParts['url'][$this->type->value]) && $this->linkParts['url'][$this->type->value] instanceof ($this->type->getResourceType())) { - return [ - 'data-linkbrowser-current-link' => GeneralUtility::makeInstance(LinkService::class)->asString([ - 'type' => $this->type->getLinkServiceType(), - $this->type->value => $this->linkParts['url'][$this->type->value], - ]), - ]; + $resource = $this->linkParts['url'][$this->type->value] ?? null; + if (!$resource instanceof ($this->type->getResourceType())) { + return []; + } + if (!$resource->checkActionPermission('read')) { + return []; + } + if ($resource->getStorage()->isFallbackStorage()) { + return []; } - return []; + return [ + 'data-linkbrowser-current-link' => GeneralUtility::makeInstance(LinkService::class)->asString([ + 'type' => $this->type->getLinkServiceType(), + $this->type->value => $resource, + ]), + ]; } protected function createUri(ServerRequestInterface $request, array $parameters = []): string
2de87ff113ba[SECURITY] Prevent arbitrary access to privileged resources via t3://
19 files changed · +79 −33
Build/phpstan/phpstan-baseline.neon+0 −5 modified@@ -110,11 +110,6 @@ parameters: count: 1 path: ../../typo3/sysext/backend/Classes/Form/Element/ImageManipulationElement.php - - - message: "#^If condition is always true\\.$#" - count: 2 - path: ../../typo3/sysext/backend/Classes/Form/Element/InputLinkElement.php - - message: "#^Right side of && is always true\\.$#" count: 1
typo3/sysext/backend/Classes/Backend/Shortcut/ShortcutRepository.php+1 −1 modified@@ -509,7 +509,7 @@ protected function initShortcuts(): array $combinedIdentifier = (string)($arguments['id'] ?? ''); if ($combinedIdentifier !== '') { $storage = GeneralUtility::makeInstance(StorageRepository::class)->findByCombinedIdentifier($combinedIdentifier); - if ($storage === null || $storage->getUid() === 0) { + if ($storage === null || $storage->isFallbackStorage()) { // Continue, if invalid storage or disallowed fallback storage continue; }
typo3/sysext/backend/Classes/Form/Element/InputLinkElement.php+11 −6 modified@@ -27,6 +27,7 @@ use TYPO3\CMS\Core\Resource\Exception\InvalidPathException; use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\Folder; +use TYPO3\CMS\Core\Type\Bitmask\Permission; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Core\Utility\StringUtility; @@ -355,10 +356,12 @@ protected function getLinkExplanation(string $itemValue): array } } + $backendUser = $this->getBackendUser(); // Resolve the actual link switch ($linkData['type']) { case LinkService::TYPE_PAGE: - $pageRecord = BackendUtility::readPageAccess($linkData['pageuid'] ?? null, '1=1'); + $pagePermissionClause = $backendUser->getPagePermsClause(Permission::PAGE_SHOW); + $pageRecord = BackendUtility::readPageAccess($linkData['pageuid'] ?? null, $pagePermissionClause); // Is this a real page if ($pageRecord['uid'] ?? 0) { $fragmentTitle = ''; @@ -391,19 +394,19 @@ protected function getLinkExplanation(string $itemValue): array ]; break; case LinkService::TYPE_FILE: - /** @var File $file */ + /** @var File|null $file */ $file = $linkData['file']; - if ($file) { + if ($file && $file->checkActionPermission('read') && !$file->getStorage()->isFallbackStorage()) { $data = [ 'text' => $file->getPublicUrl(), 'icon' => $this->iconFactory->getIconForFileExtension($file->getExtension(), Icon::SIZE_SMALL)->render(), ]; } break; case LinkService::TYPE_FOLDER: - /** @var Folder $folder */ + /** @var Folder|null $folder */ $folder = $linkData['folder']; - if ($folder) { + if ($folder && $folder->checkActionPermission('read') && !$folder->getStorage()->isFallbackStorage()) { $data = [ 'text' => $folder->getPublicUrl(), 'icon' => $this->iconFactory->getIcon('apps-filetree-folder-default', Icon::SIZE_SMALL)->render(), @@ -413,7 +416,9 @@ protected function getLinkExplanation(string $itemValue): array case LinkService::TYPE_RECORD: $table = $this->data['pageTsConfig']['TCEMAIN.']['linkHandler.'][$linkData['identifier'] . '.']['configuration.']['table'] ?? ''; $record = BackendUtility::getRecord($table, $linkData['uid']); - if ($record) { + $pagePermissionClause = $backendUser->getPagePermsClause(Permission::PAGE_SHOW); + $hasPageAccess = BackendUtility::readPageAccess($record['pid'] ?? null, $pagePermissionClause) !== false; + if ($record && $hasPageAccess && $backendUser->check('tables_select', $table)) { $recordTitle = BackendUtility::getRecordTitle($table, $record); $tableTitle = $this->getLanguageService()->sL($GLOBALS['TCA'][$table]['ctrl']['title']); $data = [
typo3/sysext/core/Classes/LinkHandling/FileLinkHandler.php+8 −0 modified@@ -20,6 +20,7 @@ use TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException; use TYPO3\CMS\Core\Resource\FileInterface; use TYPO3\CMS\Core\Resource\ResourceFactory; +use TYPO3\CMS\Core\Resource\Security\FileNameValidator; use TYPO3\CMS\Core\Utility\GeneralUtility; /** @@ -77,6 +78,13 @@ public function resolveHandlerData(array $data): array { try { $file = $this->resolveFile($data); + $fileNameValidator = GeneralUtility::makeInstance(FileNameValidator::class); + if ( + !$fileNameValidator->isValid(basename($file->getIdentifier())) || + !$fileNameValidator->isValid($file->getName()) + ) { + $file = null; + } } catch (FileDoesNotExistException $e) { $file = null; }
typo3/sysext/core/Classes/Resource/ResourceStorage.php+29 −6 modified@@ -356,6 +356,17 @@ public function hasChildren() return true; } + /** + * Returns true if this storage is a virtual storage that provides + * access to all files in the project root. + * + * @internal + */ + public function isFallbackStorage(): bool + { + return $this->getUid() === 0; + } + /********************************* * Capabilities ********************************/ @@ -727,7 +738,7 @@ public function checkFileActionPermission($action, FileInterface $file) return false; } // Check 3: No action allowed on files for denied file extensions - if (!$this->checkFileExtensionPermission($file->getName())) { + if (!$this->checkValidFileExtension($file)) { return false; } $isReadCheck = false; @@ -839,6 +850,17 @@ protected function checkFileExtensionPermission($fileName) return GeneralUtility::makeInstance(FileNameValidator::class)->isValid($fileName); } + /** + * Check file extension of an existing file against the + * current file deny pattern. + */ + protected function checkValidFileExtension(FileInterface $file): bool + { + $fileNameValidator = GeneralUtility::makeInstance(FileNameValidator::class); + return $fileNameValidator->isValid($file->getName()) && + $fileNameValidator->isValid(basename($file->getIdentifier())); + } + /** * Assures read permission for given folder. * @@ -906,7 +928,7 @@ protected function assureFileReadPermission(FileInterface $file) 1375955429 ); } - if (!$this->checkFileExtensionPermission($file->getName())) { + if (!$this->checkValidFileExtension($file)) { throw new IllegalFileExtensionException( 'You are not allowed to use that file extension. File: "' . $file->getName() . '"', 1375955430 @@ -928,7 +950,7 @@ protected function assureFileWritePermissions(FileInterface $file) if (!$this->checkFileActionPermission('write', $file)) { throw new InsufficientFileWritePermissionsException('Writing to file "' . $file->getIdentifier() . '" is not allowed.', 1330121088); } - if (!$this->checkFileExtensionPermission($file->getName())) { + if (!$this->checkValidFileExtension($file)) { throw new IllegalFileExtensionException('You are not allowed to edit a file with extension "' . $file->getExtension() . '"', 1366711933); } } @@ -963,7 +985,7 @@ protected function assureFileReplacePermissions(FileInterface $file) protected function assureFileDeletePermissions(FileInterface $file) { // Check for disallowed file extensions - if (!$this->checkFileExtensionPermission($file->getName())) { + if (!$this->checkValidFileExtension($file)) { throw new IllegalFileExtensionException('You are not allowed to delete a file with extension "' . $file->getExtension() . '"', 1377778916); } // Check further permissions if file is not a processed file @@ -1082,7 +1104,7 @@ protected function assureFileMovePermissions(FileInterface $file, Folder $target protected function assureFileRenamePermissions(FileInterface $file, $targetFileName) { // Check if file extension is allowed - if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkFileExtensionPermission($file->getName())) { + if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkValidFileExtension($file)) { throw new IllegalFileExtensionException('You are not allowed to rename a file with this extension. File given: "' . $file->getName() . '"', 1371466663); } // Check if user is allowed to rename @@ -1127,7 +1149,7 @@ protected function assureFileCopyPermissions(FileInterface $file, Folder $target throw new InsufficientFolderWritePermissionsException('You are not allowed to write to the target folder "' . $targetFolder->getIdentifier() . '"', 1319550435); } // Check for a valid file extension - if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkFileExtensionPermission($file->getName())) { + if (!$this->checkFileExtensionPermission($targetFileName) || !$this->checkValidFileExtension($file)) { throw new IllegalFileExtensionException('You are not allowed to copy a file of that type.', 1319553317); } } @@ -1779,6 +1801,7 @@ public function streamFile( string $alternativeFilename = null, string $overrideMimeType = null ): ResponseInterface { + $this->assureFileReadPermission($file); if (!$this->driver instanceof StreamableDriverInterface) { return $this->getPseudoStream($file, $asDownload, $alternativeFilename, $overrideMimeType); }
typo3/sysext/core/Classes/Resource/Security/StoragePermissionsAspect.php+2 −5 modified@@ -42,13 +42,10 @@ public function addUserPermissionsToStorage(AfterResourceStorageInitializationEv if (($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface && ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isBackend() && !$GLOBALS['BE_USER']->isAdmin() + && !$storage->isFallbackStorage() ) { $storage->setEvaluatePermissions(true); - if ($storage->getUid() > 0) { - $storage->setUserPermissions($GLOBALS['BE_USER']->getFilePermissionsForStorage($storage)); - } else { - $storage->setEvaluatePermissions(false); - } + $storage->setUserPermissions($GLOBALS['BE_USER']->getFilePermissionsForStorage($storage)); $this->addFileMountsToStorage($storage); } }
typo3/sysext/core/Classes/Utility/File/ExtendedFileUtility.php+1 −1 modified@@ -627,7 +627,7 @@ protected function getFileObject($identifier) if (!is_object($object)) { throw new InvalidFileException('The item ' . $identifier . ' was not a file or directory!!', 1320122453); } - if ($object->getStorage()->getUid() === 0) { + if ($object->getStorage()->isFallbackStorage()) { throw new InsufficientFileAccessPermissionsException('You are not allowed to access files outside your storages', 1375889830); } return $object;
typo3/sysext/core/Tests/Unit/DataHandling/SoftReference/TypoLinkSoftReferenceParserTest.php+2 −0 modified@@ -236,6 +236,8 @@ public function findRefReturnsParsedElementsWithFile(array $softrefConfiguration { $fileObject = $this->prophesize(File::class); $fileObject->getUid()->willReturn(42)->shouldBeCalledTimes(1); + $fileObject->getName()->willReturn('download.jpg'); + $fileObject->getIdentifier()->willReturn('fileadmin/download.jpg'); $resourceFactory = $this->prophesize(ResourceFactory::class); $resourceFactory->getFileObject('42')->willReturn($fileObject->reveal());
typo3/sysext/core/Tests/Unit/DataHandling/SoftReference/TypoLinkTagSoftReferenceParserTest.php+2 −0 modified@@ -180,6 +180,8 @@ public function findRefReturnsParsedElementsWithFile(array $softrefConfiguration { $fileObject = $this->prophesize(File::class); $fileObject->getUid()->willReturn(42)->shouldBeCalledTimes(1); + $fileObject->getName()->willReturn('download.jpg'); + $fileObject->getIdentifier()->willReturn('fileadmin/download.jpg'); $resourceFactory = $this->prophesize(ResourceFactory::class); $resourceFactory->getFileObject('42')->willReturn($fileObject->reveal());
typo3/sysext/core/Tests/UnitDeprecated/Database/SoftReferenceIndexTest.php+2 −0 modified@@ -353,6 +353,8 @@ public function findRefReturnsParsedElementsWithFileDataProvider(): array public function findRefReturnsParsedElementsWithFile(array $softrefConfiguration, array $expectedElement): void { $fileObject = $this->prophesize(File::class); + $fileObject->getName()->willReturn('download.jpg'); + $fileObject->getIdentifier()->willReturn('fileadmin/download.jpg'); $fileObject->getUid()->willReturn(42)->shouldBeCalledTimes(count($softrefConfiguration)); $resourceFactory = $this->prophesize(ResourceFactory::class);
typo3/sysext/core/Tests/Unit/LinkHandling/FileLinkHandlerTest.php+1 −1 modified@@ -106,7 +106,7 @@ public function resolveFileReferencesToSplitParameters(array $input, array $expe ->getMock(); // fake methods to return proper objects - $fileObject = new File(['identifier' => $expected['file'], 'name' => 'foobar.txt'], $storage); + $fileObject = new File(['identifier' => 'fileadmin/deep/down.jpg', 'name' => 'down.jpg'], $storage); $factory->method('getFileObject')->with($expected['file'])->willReturn($fileObject); $factory->method('getFileObjectFromCombinedIdentifier')->with($expected['file'])->willReturn($fileObject); $expected['file'] = $fileObject;
typo3/sysext/filelist/Classes/Controller/File/CreateFolderController.php+1 −1 modified@@ -145,7 +145,7 @@ protected function init(ServerRequestInterface $request): void $message = $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:targetNoDir'); throw new \RuntimeException($title . ': ' . $message, 1294586845); } - if ($this->folderObject->getStorage()->getUid() === 0) { + if ($this->folderObject->getStorage()->isFallbackStorage()) { throw new InsufficientFolderAccessPermissionsException( 'You are not allowed to access folders outside your storages', 1375889838
typo3/sysext/filelist/Classes/Controller/File/EditFileController.php+1 −1 modified@@ -146,7 +146,7 @@ protected function init(ServerRequestInterface $request): void $message = $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:targetNoDir'); throw new \RuntimeException($title . ': ' . $message, 1294586841); } - if ($this->fileObject->getStorage()->getUid() === 0) { + if ($this->fileObject->getStorage()->isFallbackStorage()) { throw new InsufficientFileAccessPermissionsException( 'You are not allowed to access files outside your storages', 1375889832
typo3/sysext/filelist/Classes/Controller/File/FileUploadController.php+1 −1 modified@@ -132,7 +132,7 @@ protected function init(ServerRequestInterface $request): void if ($this->target) { $this->folderObject = $this->resourceFactory->retrieveFileOrFolderObject($this->target); } - if ($this->folderObject->getStorage()->getUid() === 0) { + if ($this->folderObject->getStorage()->isFallbackStorage()) { throw new InsufficientFolderAccessPermissionsException( 'You are not allowed to access folders outside your storages', 1375889834
typo3/sysext/filelist/Classes/Controller/FileListController.php+1 −1 modified@@ -127,7 +127,7 @@ public function handleRequest(ServerRequestInterface $request): ResponseInterfac } $this->folderObject = $storage->getFolder($identifier); // Disallow access to fallback storage 0 - if ($storage->getUid() === 0) { + if ($storage->isFallbackStorage()) { throw new InsufficientFolderAccessPermissionsException( 'You are not allowed to access files outside your storages', 1434539815
typo3/sysext/filelist/Classes/Controller/File/RenameFileController.php+1 −1 modified@@ -128,7 +128,7 @@ protected function init(ServerRequestInterface $request): void $message = $this->getLanguageService()->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:targetNoDir'); throw new \RuntimeException($title . ': ' . $message, 1294586844); } - if ($this->fileOrFolderObject->getStorage()->getUid() === 0) { + if ($this->fileOrFolderObject->getStorage()->isFallbackStorage()) { throw new InsufficientFileAccessPermissionsException('You are not allowed to access files outside your storages', 1375889840); }
typo3/sysext/filelist/Classes/Controller/File/ReplaceFileController.php+1 −1 modified@@ -128,7 +128,7 @@ protected function init(ServerRequestInterface $request): void $message = $lang->sL('LLL:EXT:filelist/Resources/Private/Language/locallang_mod_file_list.xlf:targetNoDir'); throw new \RuntimeException($title . ': ' . $message, 1436895930); } - if ($this->fileOrFolderObject->getStorage()->getUid() === 0) { + if ($this->fileOrFolderObject->getStorage()->isFallbackStorage()) { throw new InsufficientFileAccessPermissionsException( 'You are not allowed to access files outside your storages', 1436895931
typo3/sysext/recordlist/Classes/LinkHandler/FileLinkHandler.php+3 −0 modified@@ -123,6 +123,9 @@ public function render(ServerRequestInterface $request) // Create upload/create folder forms, if a path is given $selectedFolder = $this->getSelectedFolder($this->expandFolder); + if ($selectedFolder->getStorage()->isFallbackStorage()) { + $selectedFolder = null; + } // Build the file upload and folder creation form if ($selectedFolder) {
typo3/sysext/recordlist/Classes/LinkHandler/PageLinkHandler.php+11 −2 modified@@ -24,6 +24,7 @@ use TYPO3\CMS\Core\Domain\Repository\PageRepository; use TYPO3\CMS\Core\Imaging\Icon; use TYPO3\CMS\Core\LinkHandling\LinkService; +use TYPO3\CMS\Core\Type\Bitmask\Permission; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface; @@ -86,11 +87,19 @@ public function formatCurrentUrl() $titleLen = (int)$this->getBackendUser()->uc['titleLen']; $id = (int)$this->linkParts['url']['pageuid']; - $pageTitle = BackendUtility::getRecordWSOL('pages', $id, 'title')['title'] ?? ''; + $idInfo = 'ID: ' . $id . (!empty($this->linkParts['url']['fragment']) ? ', #' . $this->linkParts['url']['fragment'] : ''); + + $permsClause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW); + $pageRecord = BackendUtility::readPageAccess($id, $permsClause); + if ($pageRecord === false) { + return $lang->getLL('page') . ' ' . $idInfo; + } + + $pageTitle = $pageRecord['title'] ?? ''; return $lang->getLL('page') . ($pageTitle ? ' \'' . GeneralUtility::fixed_lgd_cs($pageTitle, $titleLen) . '\'' : '') - . ' (ID: ' . $id . (!empty($this->linkParts['url']['fragment']) ? ', #' . $this->linkParts['url']['fragment'] : '') . ')'; + . ' (' . $idInfo . ')'; } /**
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-wf85-8hx9-gj7cghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-25120ghsaADVISORY
- docs.typo3.org/m/typo3/reference-typoscript/main/en-us/Functions/Typolink.htmlghsax_refsource_MISCWEB
- github.com/TYPO3/typo3/commit/2de87ff113ba24333ab7cbb8078588743f8958d6ghsaWEB
- github.com/TYPO3/typo3/commit/33f4d279b82bca0a509227a17065244c6156e68fghsaWEB
- github.com/TYPO3/typo3/commit/ae0dfc4c058a90c10eedb3f49cfaf33164d21cddghsaWEB
- github.com/TYPO3/typo3/security/advisories/GHSA-wf85-8hx9-gj7cghsax_refsource_CONFIRMWEB
- typo3.org/security/advisory/typo3-core-sa-2024-005ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.