VYPR
Moderate severityNVD Advisory· Published Feb 13, 2024· Updated Apr 24, 2025

Improper Access Control of Resources Referenced by t3:// URI Scheme in TYPO3

CVE-2024-25120

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.

PackageAffected versionsPatched versions
typo3/cms-corePackagist
>= 8.0.0, < 8.7.578.7.57
typo3/cms-corePackagist
>= 9.0.0, < 9.5.469.5.46
typo3/cms-corePackagist
>= 10.0.0, < 10.4.4310.4.43
typo3/cms-corePackagist
>= 11.0.0, < 11.5.3511.5.35
typo3/cms-corePackagist
>= 12.0.0, < 12.4.1112.4.11
typo3/cms-corePackagist
>= 13.0.0, < 13.0.113.0.1

Affected products

2

Patches

3
ae0dfc4c058a

[SECURITY] Prevent arbitrary access to privileged resources via t3://

https://github.com/TYPO3/typo3Benjamin FranzkeFeb 13, 2024via ghsa
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://

https://github.com/TYPO3/typo3Benjamin FranzkeFeb 13, 2024via ghsa
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://

https://github.com/TYPO3/typo3Benjamin FranzkeFeb 13, 2024via ghsa
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

News mentions

0

No linked articles in our index yet.