VYPR
Moderate severityNVD Advisory· Published Dec 25, 2023· Updated Aug 2, 2024

CVE-2023-30451

CVE-2023-30451

Description

In TYPO3 11.5.24, the filelist component allows attackers (who have access to the administrator panel) to read arbitrary files via directory traversal in the baseuri field, as demonstrated by POST /typo3/record/edit with ../../../ in data[sys_file_storage]*[data][sDEF][lDEF][basePath][vDEF].

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

TYPO3 11.5.24 filelist component allows authenticated admin users to read arbitrary files via directory traversal in the basePath field of File Abstraction Layer storages.

Vulnerability

Description

CVE-2023-30451 is a path traversal vulnerability in TYPO3's File Abstraction Layer (FAL) component, affecting versions 8.0.0 through 13.0.0 [3][4]. The root cause is that configurable storages using the local driver could be configured to access directories outside the project root, and the system setting BE/lockRootPath was not evaluated by the FAL component [3][4]. This allows an attacker to specify arbitrary base paths for file storages.

Exploitation

Exploitation requires an administrator-level backend user account [1][3]. An attacker can send a crafted POST request to /typo3/record/edit with directory traversal sequences (e.g., ../../../) in the data[sys_file_storage]*[data][sDEF][lDEF][basePath][vDEF] field [1]. This bypasses intended access restrictions and allows reading files outside the web root.

Impact

Successful exploitation enables an authenticated admin to read arbitrary files on the server, potentially exposing sensitive configuration files, credentials, or other confidential data [1][3]. The vulnerability is rated medium severity with a CVSS v3.1 score of 6.5 (AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:N) [3].

Mitigation

TYPO3 has released patches in 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 [3][4]. Additionally, the lockRootPath setting is now enforced and supports an array of allowed directories; storages referencing unapproved directories are marked offline [3][4]. Administrators should update immediately and review storage configurations.

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
205115cca3d6

[!!!][SECURITY] Enforce absolute path checks in FAL local driver

https://github.com/TYPO3/typo3Oliver HaderFeb 13, 2024via ghsa
8 files changed · +127 7
  • typo3/sysext/core/Classes/Resource/Driver/LocalDriver.php+15 0 modified
    @@ -157,6 +157,12 @@ protected function calculateBasePath(array $configuration): string
             }
             $absoluteBasePath = $this->canonicalizeAndCheckFilePath($absoluteBasePath);
             $absoluteBasePath = rtrim($absoluteBasePath, '/') . '/';
    +        if (!$this->isAllowedAbsolutePath($absoluteBasePath)) {
    +            throw new InvalidConfigurationException(
    +                'Base path "' . $absoluteBasePath . '" is not within the allowed project root path or allowed lockRootPath.',
    +                1704807715
    +            );
    +        }
             if (!is_dir($absoluteBasePath)) {
                 throw new InvalidConfigurationException(
                     'Base path "' . $absoluteBasePath . '" does not exist or is no directory.',
    @@ -1360,4 +1366,13 @@ protected function getRecycleDirectory(string $path): string
     
             return '';
         }
    +
    +    /**
    +     * Wrapper for `GeneralUtility::isAllowedAbsPath`, which implicitly invokes
    +     * `GeneralUtility::validPathStr` (like in `parent::isPathValid`).
    +     */
    +    protected function isAllowedAbsolutePath(string $path): bool
    +    {
    +        return GeneralUtility::isAllowedAbsPath($path);
    +    }
     }
    
  • typo3/sysext/core/Classes/Utility/GeneralUtility.php+1 2 modified
    @@ -2478,12 +2478,11 @@ public static function isAllowedAbsPath(string $path): bool
             if (substr($path, 0, 6) === 'vfs://') {
                 return true;
             }
    -        $lockRootPath = $GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] ?? '';
             return PathUtility::isAbsolutePath($path) && static::validPathStr($path)
                 && (
                     str_starts_with($path, Environment::getProjectPath())
                     || str_starts_with($path, Environment::getPublicPath())
    -                || ($lockRootPath && str_starts_with($path, $lockRootPath))
    +                || PathUtility::isAllowedAdditionalPath($path)
                 );
         }
     
    
  • typo3/sysext/core/Classes/Utility/PathUtility.php+29 0 modified
    @@ -446,4 +446,33 @@ public static function hasProtocolAndScheme(string $path): bool
         {
             return str_starts_with($path, '//') || strpos($path, '://') > 0;
         }
    +
    +    /**
    +     * Evaluates a given path against the optional settings in `$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath']`.
    +     * Albeit the name `BE/lockRootPath` is misleading, this setting was and is used in general and is not limited
    +     * to the backend-scope. The setting actually allows defining additional paths, besides the project root path.
    +     *
    +     * @param string $path Absolute path to a file or directory
    +     */
    +    public static function isAllowedAdditionalPath(string $path): bool
    +    {
    +        // ensure the submitted path ends with a string, even for a file
    +        $path = self::sanitizeTrailingSeparator($path);
    +        $allowedPaths = $GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] ?? [];
    +        if (is_string($allowedPaths)) {
    +            // The setting was a string before and is now an array
    +            // For compatibility reasons, we cast a string to an array here for now
    +            $allowedPaths = [$allowedPaths];
    +        }
    +        if (!is_array($allowedPaths)) {
    +            throw new \RuntimeException('$GLOBALS[\'TYPO3_CONF_VARS\'][\'BE\'][\'lockRootPath\'] is expected to be an array.', 1707408379);
    +        }
    +        foreach ($allowedPaths as $allowedPath) {
    +            $allowedPath = trim($allowedPath);
    +            if ($allowedPath !== '' && str_starts_with($path, self::sanitizeTrailingSeparator($allowedPath))) {
    +                return true;
    +            }
    +        }
    +        return false;
    +    }
     }
    
  • typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml+2 2 modified
    @@ -226,8 +226,8 @@ BE:
                 type: text
                 description: 'Path to the primary directory of files for editors. This is relative to the public web dir, DefaultStorage will be created with that configuration, do not access manually but via <code>\TYPO3\CMS\Core\Resource\ResourceFactory::getDefaultStorage().</code>'
             lockRootPath:
    -            type: text
    -            description: 'This path is used to evaluate if paths outside of public web path should be allowed. Ending slash required!'
    +            type: array
    +            description: 'List of absolute root path prefixes to be allowed for file operations (including FAL storages). The project root path is allowed in any case and does not need to be defined here. Ending slashes are enforced!'
             userHomePath:
                 type: text
                 description: 'Combined folder identifier of the directory where TYPO3 backend-users have their home-dirs. A combined folder identifier looks like this: [storageUid]:[folderIdentifier]. Eg. <code>2:users/</code>. A home for backend user 2 would be: <code>2:users/2/</code>. Ending slash required!'
    
  • typo3/sysext/core/Documentation/Changelog/11.5.x/Important-102800-FileAbstractionLayerEnforcesAbsolutePathsToMatchProjectRootOrLockRootPath.rst+39 0 added
    @@ -0,0 +1,39 @@
    +.. include:: /Includes.rst.txt
    +
    +.. _important-102800-1707409544:
    +
    +=========================================================================================================
    +Important: #102800 - File Abstraction Layer enforces absolute paths to match project root or lockRootPath
    +=========================================================================================================
    +
    +See :issue:`102800`
    +
    +Description
    +===========
    +
    +
    +The File Abstraction Layer Local Driver has been adapted to verify whether a
    +given absolute file path is allowed in order to prevent access to files outside
    +the project root or to the additional root path restrictions defined in
    +:php:`$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath']`.
    +
    +The option :php:`$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath']` has been
    +extended to support an array of root path prefixes to allow for multiple storages
    +to be listed. Beware that trailing slashes are enforced automatically.
    +
    +It is suggested to use the new array-based syntax, which will be applied automatically
    +once this setting is updated via Install Tool Configuration Wizard:
    +
    +..  code-block:: php
    +
    +    // Before
    +    $GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] = '/var/extra-storage';
    +
    +    // After
    +    $GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] =  [
    +        '/var/extra-storage1/',
    +        '/var/extra-storage2/',
    +    ];
    +
    +
    +.. index:: FAL, LocalConfiguration, ext:core
    
  • typo3/sysext/core/Tests/Unit/Utility/PathUtilityTest.php+37 0 modified
    @@ -560,4 +560,41 @@ public function hasProtocolAndScheme(string $url, bool $result): void
         {
             self::assertSame($result, PathUtility::hasProtocolAndScheme($url));
         }
    +
    +    public static function allowedAdditionalPathsAreEvaluatedDataProvider(): \Generator
    +    {
    +        // empty settings
    +        yield [null, '/var/shared', false];
    +        yield ['', '/var/shared', false];
    +        yield [' ', '/var/shared', false];
    +        yield [[], '/var/shared', false];
    +        yield [[''], '/var/shared', false];
    +        yield [[' '], '/var/shared', false];
    +        // string settings
    +        yield ['/var', '/var/shared', true];
    +        yield ['/var/shared/', '/var/shared', true];
    +        yield ['/var/shared', '/var/shared/', true];
    +        yield ['/var/shared/', '/var/shared/', true];
    +        yield ['/var/shared/', '/var/shared/file.png', true];
    +        yield ['/var/shared/', '/var/shared-secret', false];
    +        yield ['/var/shared/', '/var', false];
    +        // array settings
    +        yield [['/var'], '/var/shared', true];
    +        yield [['/var/shared/'], '/var/shared', true];
    +        yield [['/var/shared'], '/var/shared/', true];
    +        yield [['/var/shared/'], '/var/shared/', true];
    +        yield [['/var/shared/'], '/var/shared/file.png', true];
    +        yield [['/var/shared/'], '/var/shared-secret', false];
    +        yield [['/var/shared/'], '/var', false];
    +    }
    +
    +    /**
    +     * @test
    +     * @dataProvider allowedAdditionalPathsAreEvaluatedDataProvider
    +     */
    +    public function allowedAdditionalPathsAreEvaluated(mixed $lockRootPath, string $path, bool $expectation): void
    +    {
    +        $GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] = $lockRootPath;
    +        self::assertSame($expectation, PathUtility::isAllowedAdditionalPath($path));
    +    }
     }
    
  • typo3/sysext/filelist/Classes/Controller/FileListController.php+2 1 modified
    @@ -44,6 +44,7 @@
     use TYPO3\CMS\Core\Page\PageRenderer;
     use TYPO3\CMS\Core\Resource\Enum\DuplicationBehavior;
     use TYPO3\CMS\Core\Resource\Exception;
    +use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException;
     use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
     use TYPO3\CMS\Core\Resource\Folder;
     use TYPO3\CMS\Core\Resource\ResourceFactory;
    @@ -150,7 +151,7 @@ public function handleRequest(ServerRequestInterface $request): ResponseInterfac
                 if ($this->folderObject && !$this->folderObject->getStorage()->isWithinFileMountBoundaries($this->folderObject)) {
                     throw new \RuntimeException('Folder not accessible.', 1430409089);
                 }
    -        } catch (InsufficientFolderAccessPermissionsException $permissionException) {
    +        } catch (FolderDoesNotExistException|InsufficientFolderAccessPermissionsException $permissionException) {
                 $this->folderObject = null;
                 if ($storage !== null && $storage->getDriverType() === 'Local' && !$storage->isOnline()) {
                     // If the base folder for a local storage does not exists, the storage is marked as offline and the
    
  • typo3/sysext/filelist/Resources/Private/Language/locallang_mod_file_list.xlf+2 2 modified
    @@ -73,10 +73,10 @@
     				<source>You have no access to the folder "%s".</source>
     			</trans-unit>
     			<trans-unit id="localStorageOfflineTitle" resname="localStorageOfflineTitle">
    -				<source>Base folder for local storage missing</source>
    +				<source>Base folder for local storage missing or not allowed</source>
     			</trans-unit>
     			<trans-unit id="localStorageOfflineMessage" resname="localStorageOfflineMessage">
    -				<source>Base folder for local storage does not exists. Verify that the base folder for "%s" exists.</source>
    +				<source>Verify that the base folder for the storage "%s" exists and is allowed to be accessed.</source>
     			</trans-unit>
     			<trans-unit id="folderNotFoundTitle" resname="folderNotFoundTitle">
     				<source>Folder not found.</source>
    
78fb9287a2f0

[!!!][SECURITY] Enforce absolute path checks in FAL local driver

https://github.com/TYPO3/typo3Oliver HaderFeb 13, 2024via ghsa
8 files changed · +127 7
  • typo3/sysext/core/Classes/Resource/Driver/LocalDriver.php+15 0 modified
    @@ -165,6 +165,12 @@ protected function calculateBasePath(array $configuration)
             }
             $absoluteBasePath = $this->canonicalizeAndCheckFilePath($absoluteBasePath);
             $absoluteBasePath = rtrim($absoluteBasePath, '/') . '/';
    +        if (!$this->isAllowedAbsolutePath($absoluteBasePath)) {
    +            throw new InvalidConfigurationException(
    +                'Base path "' . $absoluteBasePath . '" is not within the allowed project root path or allowed lockRootPath.',
    +                1704807715
    +            );
    +        }
             if (!is_dir($absoluteBasePath)) {
                 throw new InvalidConfigurationException(
                     'Base path "' . $absoluteBasePath . '" does not exist or is no directory.',
    @@ -1464,4 +1470,13 @@ protected function getRecycleDirectory($path)
     
             return '';
         }
    +
    +    /**
    +     * Wrapper for `GeneralUtility::isAllowedAbsPath`, which implicitly invokes
    +     * `GeneralUtility::validPathStr` (like in `parent::isPathValid`).
    +     */
    +    protected function isAllowedAbsolutePath(string $path): bool
    +    {
    +        return GeneralUtility::isAllowedAbsPath($path);
    +    }
     }
    
  • typo3/sysext/core/Classes/Utility/GeneralUtility.php+1 2 modified
    @@ -2659,12 +2659,11 @@ public static function isAllowedAbsPath($path)
             if (substr($path, 0, 6) === 'vfs://') {
                 return true;
             }
    -        $lockRootPath = $GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] ?? '';
             return PathUtility::isAbsolutePath($path) && static::validPathStr($path)
                 && (
                     str_starts_with($path, Environment::getProjectPath())
                     || str_starts_with($path, Environment::getPublicPath())
    -                || ($lockRootPath && str_starts_with($path, $lockRootPath))
    +                || PathUtility::isAllowedAdditionalPath($path)
                 );
         }
     
    
  • typo3/sysext/core/Classes/Utility/PathUtility.php+29 0 modified
    @@ -446,4 +446,33 @@ public static function hasProtocolAndScheme(string $path): bool
         {
             return str_starts_with($path, '//') || strpos($path, '://') > 0;
         }
    +
    +    /**
    +     * Evaluates a given path against the optional settings in `$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath']`.
    +     * Albeit the name `BE/lockRootPath` is misleading, this setting was and is used in general and is not limited
    +     * to the backend-scope. The setting actually allows defining additional paths, besides the project root path.
    +     *
    +     * @param string $path Absolute path to a file or directory
    +     */
    +    public static function isAllowedAdditionalPath(string $path): bool
    +    {
    +        // ensure the submitted path ends with a string, even for a file
    +        $path = self::sanitizeTrailingSeparator($path);
    +        $allowedPaths = $GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] ?? [];
    +        if (is_string($allowedPaths)) {
    +            // The setting was a string before and is now an array
    +            // For compatibility reasons, we cast a string to an array here for now
    +            $allowedPaths = [$allowedPaths];
    +        }
    +        if (!is_array($allowedPaths)) {
    +            throw new \RuntimeException('$GLOBALS[\'TYPO3_CONF_VARS\'][\'BE\'][\'lockRootPath\'] is expected to be an array.', 1707408379);
    +        }
    +        foreach ($allowedPaths as $allowedPath) {
    +            $allowedPath = trim($allowedPath);
    +            if ($allowedPath !== '' && str_starts_with($path, self::sanitizeTrailingSeparator($allowedPath))) {
    +                return true;
    +            }
    +        }
    +        return false;
    +    }
     }
    
  • typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml+2 2 modified
    @@ -246,8 +246,8 @@ BE:
                 type: text
                 description: 'Path to the primary directory of files for editors. This is relative to the public web dir, DefaultStorage will be created with that configuration, do not access manually but via <code>\TYPO3\CMS\Core\Resource\ResourceFactory::getDefaultStorage().</code>'
             lockRootPath:
    -            type: text
    -            description: 'This path is used to evaluate if paths outside of public web path should be allowed. Ending slash required!'
    +            type: array
    +            description: 'List of absolute root path prefixes to be allowed for file operations (including FAL storages). The project root path is allowed in any case and does not need to be defined here. Ending slashes are enforced!'
             userHomePath:
                 type: text
                 description: 'Combined folder identifier of the directory where TYPO3 backend-users have their home-dirs. A combined folder identifier looks like this: [storageUid]:[folderIdentifier]. Eg. <code>2:users/</code>. A home for backend user 2 would be: <code>2:users/2/</code>. Ending slash required!'
    
  • typo3/sysext/core/Documentation/Changelog/11.5.x/Important-102800-FileAbstractionLayerEnforcesAbsolutePathsToMatchProjectRootOrLockRootPath.rst+39 0 added
    @@ -0,0 +1,39 @@
    +.. include:: /Includes.rst.txt
    +
    +.. _important-102800-1707409544:
    +
    +=========================================================================================================
    +Important: #102800 - File Abstraction Layer enforces absolute paths to match project root or lockRootPath
    +=========================================================================================================
    +
    +See :issue:`102800`
    +
    +Description
    +===========
    +
    +
    +The File Abstraction Layer Local Driver has been adapted to verify whether a
    +given absolute file path is allowed in order to prevent access to files outside
    +the project root or to the additional root path restrictions defined in
    +:php:`$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath']`.
    +
    +The option :php:`$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath']` has been
    +extended to support an array of root path prefixes to allow for multiple storages
    +to be listed. Beware that trailing slashes are enforced automatically.
    +
    +It is suggested to use the new array-based syntax, which will be applied automatically
    +once this setting is updated via Install Tool Configuration Wizard:
    +
    +..  code-block:: php
    +
    +    // Before
    +    $GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] = '/var/extra-storage';
    +
    +    // After
    +    $GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] =  [
    +        '/var/extra-storage1/',
    +        '/var/extra-storage2/',
    +    ];
    +
    +
    +.. index:: FAL, LocalConfiguration, ext:core
    
  • typo3/sysext/core/Tests/Unit/Utility/PathUtilityTest.php+37 0 modified
    @@ -560,4 +560,41 @@ public function hasProtocolAndScheme(string $url, bool $result): void
         {
             self::assertSame($result, PathUtility::hasProtocolAndScheme($url));
         }
    +
    +    public static function allowedAdditionalPathsAreEvaluatedDataProvider(): \Generator
    +    {
    +        // empty settings
    +        yield [null, '/var/shared', false];
    +        yield ['', '/var/shared', false];
    +        yield [' ', '/var/shared', false];
    +        yield [[], '/var/shared', false];
    +        yield [[''], '/var/shared', false];
    +        yield [[' '], '/var/shared', false];
    +        // string settings
    +        yield ['/var', '/var/shared', true];
    +        yield ['/var/shared/', '/var/shared', true];
    +        yield ['/var/shared', '/var/shared/', true];
    +        yield ['/var/shared/', '/var/shared/', true];
    +        yield ['/var/shared/', '/var/shared/file.png', true];
    +        yield ['/var/shared/', '/var/shared-secret', false];
    +        yield ['/var/shared/', '/var', false];
    +        // array settings
    +        yield [['/var'], '/var/shared', true];
    +        yield [['/var/shared/'], '/var/shared', true];
    +        yield [['/var/shared'], '/var/shared/', true];
    +        yield [['/var/shared/'], '/var/shared/', true];
    +        yield [['/var/shared/'], '/var/shared/file.png', true];
    +        yield [['/var/shared/'], '/var/shared-secret', false];
    +        yield [['/var/shared/'], '/var', false];
    +    }
    +
    +    /**
    +     * @test
    +     * @dataProvider allowedAdditionalPathsAreEvaluatedDataProvider
    +     */
    +    public function allowedAdditionalPathsAreEvaluated(mixed $lockRootPath, string $path, bool $expectation): void
    +    {
    +        $GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] = $lockRootPath;
    +        self::assertSame($expectation, PathUtility::isAllowedAdditionalPath($path));
    +    }
     }
    
  • typo3/sysext/filelist/Classes/Controller/FileListController.php+2 1 modified
    @@ -43,6 +43,7 @@
     use TYPO3\CMS\Core\Page\PageRenderer;
     use TYPO3\CMS\Core\Resource\DuplicationBehavior;
     use TYPO3\CMS\Core\Resource\Exception;
    +use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException;
     use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
     use TYPO3\CMS\Core\Resource\Folder;
     use TYPO3\CMS\Core\Resource\ResourceFactory;
    @@ -148,7 +149,7 @@ public function handleRequest(ServerRequestInterface $request): ResponseInterfac
                 if ($this->folderObject && !$this->folderObject->getStorage()->isWithinFileMountBoundaries($this->folderObject)) {
                     throw new \RuntimeException('Folder not accessible.', 1430409089);
                 }
    -        } catch (InsufficientFolderAccessPermissionsException $permissionException) {
    +        } catch (FolderDoesNotExistException|InsufficientFolderAccessPermissionsException $permissionException) {
                 $this->folderObject = null;
                 if ($storage !== null && $storage->getDriverType() === 'Local' && !$storage->isOnline()) {
                     // If the base folder for a local storage does not exists, the storage is marked as offline and the
    
  • typo3/sysext/filelist/Resources/Private/Language/locallang_mod_file_list.xlf+2 2 modified
    @@ -73,10 +73,10 @@
     				<source>You have no access to the folder "%s".</source>
     			</trans-unit>
     			<trans-unit id="localStorageOfflineTitle" resname="localStorageOfflineTitle">
    -				<source>Base folder for local storage missing</source>
    +				<source>Base folder for local storage missing or not allowed</source>
     			</trans-unit>
     			<trans-unit id="localStorageOfflineMessage" resname="localStorageOfflineMessage">
    -				<source>Base folder for local storage does not exists. Verify that the base folder for "%s" exists.</source>
    +				<source>Verify that the base folder for the storage "%s" exists and is allowed to be accessed.</source>
     			</trans-unit>
     			<trans-unit id="folderNotFoundTitle" resname="folderNotFoundTitle">
     				<source>Folder not found.</source>
    
accf537c7379

[!!!][SECURITY] Enforce absolute path checks in FAL local driver

https://github.com/TYPO3/typo3Oliver HaderFeb 13, 2024via ghsa
8 files changed · +129 7
  • typo3/sysext/core/Classes/Resource/Driver/LocalDriver.php+15 0 modified
    @@ -169,6 +169,12 @@ protected function calculateBasePath(array $configuration)
             }
             $absoluteBasePath = $this->canonicalizeAndCheckFilePath($absoluteBasePath);
             $absoluteBasePath = rtrim($absoluteBasePath, '/') . '/';
    +        if (!$this->isAllowedAbsolutePath($absoluteBasePath)) {
    +            throw new InvalidConfigurationException(
    +                'Base path "' . $absoluteBasePath . '" is not within the allowed project root path or allowed lockRootPath.',
    +                1704807715
    +            );
    +        }
             if (!is_dir($absoluteBasePath)) {
                 throw new InvalidConfigurationException(
                     'Base path "' . $absoluteBasePath . '" does not exist or is no directory.',
    @@ -1473,4 +1479,13 @@ protected function getRecycleDirectory($path)
     
             return '';
         }
    +
    +    /**
    +     * Wrapper for `GeneralUtility::isAllowedAbsPath`, which implicitly invokes
    +     * `GeneralUtility::validPathStr` (like in `parent::isPathValid`).
    +     */
    +    protected function isAllowedAbsolutePath(string $path): bool
    +    {
    +        return GeneralUtility::isAllowedAbsPath($path);
    +    }
     }
    
  • typo3/sysext/core/Classes/Utility/GeneralUtility.php+1 2 modified
    @@ -2826,12 +2826,11 @@ public static function isAllowedAbsPath($path)
             if (substr($path, 0, 6) === 'vfs://') {
                 return true;
             }
    -        $lockRootPath = $GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] ?? '';
             return PathUtility::isAbsolutePath($path) && static::validPathStr($path)
                 && (
                     str_starts_with($path, Environment::getProjectPath())
                     || str_starts_with($path, Environment::getPublicPath())
    -                || ($lockRootPath && str_starts_with($path, $lockRootPath))
    +                || PathUtility::isAllowedAdditionalPath($path)
                 );
         }
     
    
  • typo3/sysext/core/Classes/Utility/PathUtility.php+29 0 modified
    @@ -464,4 +464,33 @@ public static function hasProtocolAndScheme(string $path): bool
         {
             return strpos($path, '//') === 0 || strpos($path, '://') > 0;
         }
    +
    +    /**
    +     * Evaluates a given path against the optional settings in `$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath']`.
    +     * Albeit the name `BE/lockRootPath` is misleading, this setting was and is used in general and is not limited
    +     * to the backend-scope. The setting actually allows defining additional paths, besides the project root path.
    +     *
    +     * @param string $path Absolute path to a file or directory
    +     */
    +    public static function isAllowedAdditionalPath(string $path): bool
    +    {
    +        // ensure the submitted path ends with a string, even for a file
    +        $path = self::sanitizeTrailingSeparator($path);
    +        $allowedPaths = $GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] ?? [];
    +        if (is_string($allowedPaths)) {
    +            // The setting was a string before and is now an array
    +            // For compatibility reasons, we cast a string to an array here for now
    +            $allowedPaths = [$allowedPaths];
    +        }
    +        if (!is_array($allowedPaths)) {
    +            throw new \RuntimeException('$GLOBALS[\'TYPO3_CONF_VARS\'][\'BE\'][\'lockRootPath\'] is expected to be an array.', 1707408379);
    +        }
    +        foreach ($allowedPaths as $allowedPath) {
    +            $allowedPath = trim($allowedPath);
    +            if ($allowedPath !== '' && str_starts_with($path, self::sanitizeTrailingSeparator($allowedPath))) {
    +                return true;
    +            }
    +        }
    +        return false;
    +    }
     }
    
  • typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml+2 2 modified
    @@ -255,8 +255,8 @@ BE:
                 type: text
                 description: 'Path to the primary directory of files for editors. This is relative to the public web dir, DefaultStorage will be created with that configuration, do not access manually but via <code>\TYPO3\CMS\Core\Resource\ResourceFactory::getDefaultStorage().</code>'
             lockRootPath:
    -            type: text
    -            description: 'This path is used to evaluate if paths outside of public web path should be allowed. Ending slash required!'
    +            type: array
    +            description: 'List of absolute root path prefixes to be allowed for file operations (including FAL storages). The project root path is allowed in any case and does not need to be defined here. Ending slashes are enforced!'
             userHomePath:
                 type: text
                 description: 'Combined folder identifier of the directory where TYPO3 backend-users have their home-dirs. A combined folder identifier looks like this: [storageUid]:[folderIdentifier]. Eg. <code>2:users/</code>. A home for backend user 2 would be: <code>2:users/2/</code>. Ending slash required!'
    
  • typo3/sysext/core/Documentation/Changelog/11.5.x/Important-102800-FileAbstractionLayerEnforcesAbsolutePathsToMatchProjectRootOrLockRootPath.rst+39 0 added
    @@ -0,0 +1,39 @@
    +.. include:: /Includes.rst.txt
    +
    +.. _important-102800-1707409544:
    +
    +=========================================================================================================
    +Important: #102800 - File Abstraction Layer enforces absolute paths to match project root or lockRootPath
    +=========================================================================================================
    +
    +See :issue:`102800`
    +
    +Description
    +===========
    +
    +
    +The File Abstraction Layer Local Driver has been adapted to verify whether a
    +given absolute file path is allowed in order to prevent access to files outside
    +the project root or to the additional root path restrictions defined in
    +:php:`$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath']`.
    +
    +The option :php:`$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath']` has been
    +extended to support an array of root path prefixes to allow for multiple storages
    +to be listed. Beware that trailing slashes are enforced automatically.
    +
    +It is suggested to use the new array-based syntax, which will be applied automatically
    +once this setting is updated via Install Tool Configuration Wizard:
    +
    +..  code-block:: php
    +
    +    // Before
    +    $GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] = '/var/extra-storage';
    +
    +    // After
    +    $GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] =  [
    +        '/var/extra-storage1/',
    +        '/var/extra-storage2/',
    +    ];
    +
    +
    +.. index:: FAL, LocalConfiguration, ext:core
    
  • typo3/sysext/core/Tests/Unit/Utility/PathUtilityTest.php+39 0 modified
    @@ -588,4 +588,43 @@ public function hasProtocolAndScheme(string $url, bool $result): void
         {
             self::assertSame($result, PathUtility::hasProtocolAndScheme($url));
         }
    +
    +    public static function allowedAdditionalPathsAreEvaluatedDataProvider(): \Generator
    +    {
    +        // empty settings
    +        yield [null, '/var/shared', false];
    +        yield ['', '/var/shared', false];
    +        yield [' ', '/var/shared', false];
    +        yield [[], '/var/shared', false];
    +        yield [[''], '/var/shared', false];
    +        yield [[' '], '/var/shared', false];
    +        // string settings
    +        yield ['/var', '/var/shared', true];
    +        yield ['/var/shared/', '/var/shared', true];
    +        yield ['/var/shared', '/var/shared/', true];
    +        yield ['/var/shared/', '/var/shared/', true];
    +        yield ['/var/shared/', '/var/shared/file.png', true];
    +        yield ['/var/shared/', '/var/shared-secret', false];
    +        yield ['/var/shared/', '/var', false];
    +        // array settings
    +        yield [['/var'], '/var/shared', true];
    +        yield [['/var/shared/'], '/var/shared', true];
    +        yield [['/var/shared'], '/var/shared/', true];
    +        yield [['/var/shared/'], '/var/shared/', true];
    +        yield [['/var/shared/'], '/var/shared/file.png', true];
    +        yield [['/var/shared/'], '/var/shared-secret', false];
    +        yield [['/var/shared/'], '/var', false];
    +    }
    +
    +    /**
    +     * @param mixed $lockRootPath
    +     *
    +     * @test
    +     * @dataProvider allowedAdditionalPathsAreEvaluatedDataProvider
    +     */
    +    public function allowedAdditionalPathsAreEvaluated($lockRootPath, string $path, bool $expectation): void
    +    {
    +        $GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath'] = $lockRootPath;
    +        self::assertSame($expectation, PathUtility::isAllowedAdditionalPath($path));
    +    }
     }
    
  • typo3/sysext/filelist/Classes/Controller/FileListController.php+2 1 modified
    @@ -37,6 +37,7 @@
     use TYPO3\CMS\Core\Page\PageRenderer;
     use TYPO3\CMS\Core\Resource\DuplicationBehavior;
     use TYPO3\CMS\Core\Resource\Exception;
    +use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException;
     use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException;
     use TYPO3\CMS\Core\Resource\Folder;
     use TYPO3\CMS\Core\Resource\ResourceFactory;
    @@ -151,7 +152,7 @@ public function handleRequest(ServerRequestInterface $request): ResponseInterfac
                 if ($this->folderObject && !$this->folderObject->getStorage()->isWithinFileMountBoundaries($this->folderObject)) {
                     throw new \RuntimeException('Folder not accessible.', 1430409089);
                 }
    -        } catch (InsufficientFolderAccessPermissionsException $permissionException) {
    +        } catch (FolderDoesNotExistException|InsufficientFolderAccessPermissionsException $permissionException) {
                 $this->folderObject = null;
                 if ($storage !== null && $storage->getDriverType() === 'Local' && !$storage->isOnline()) {
                     // If the base folder for a local storage does not exists, the storage is marked as offline and the
    
  • typo3/sysext/filelist/Resources/Private/Language/locallang_mod_file_list.xlf+2 2 modified
    @@ -76,10 +76,10 @@
     				<source>You have no access to the folder "%s".</source>
     			</trans-unit>
     			<trans-unit id="localStorageOfflineTitle" resname="localStorageOfflineTitle">
    -				<source>Base folder for local storage missing</source>
    +				<source>Base folder for local storage missing or not allowed</source>
     			</trans-unit>
     			<trans-unit id="localStorageOfflineMessage" resname="localStorageOfflineMessage">
    -				<source>Base folder for local storage does not exists. Verify that the base folder for "%s" exists.</source>
    +				<source>Verify that the base folder for the storage "%s" exists and is allowed to be accessed.</source>
     			</trans-unit>
     			<trans-unit id="folderNotFoundTitle" resname="folderNotFoundTitle">
     				<source>Folder not found.</source>
    

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.