CVE-2026-49738
Description
The path allowance check in GeneralUtility::isAllowedAbsPath() performed a plain string prefix comparison without requiring a directory separator boundary, causing a path like /var/www/html-other/secret.yaml to be incorrectly accepted as valid when the project root was /var/www/html. Administrator users with access to the File Abstraction Layer were able to create new file storage definitions pointing to directories outside the project root, bypassing this path check. This issue affects TYPO3 CMS versions before 10.4.57, 11.0.0-11.5.51, 12.0.0-12.4.46, 13.0.0-13.4.31 and 14.0.0-14.3.3.
Affected products
2Patches
244c2fa980794[SECURITY] Fix path prefix confusion in isAllowedAbsPath
4 files changed · +58 −2
typo3/sysext/core/Classes/Utility/GeneralUtility.php+2 −1 modified@@ -2071,9 +2071,10 @@ public static function validPathStr(string $theFile): bool */ public static function isAllowedAbsPath(string $path): bool { + $path = PathUtility::sanitizeTrailingSeparator($path); return PathUtility::isAbsolutePath($path) && static::validPathStr($path) && ( - str_starts_with($path, Environment::getProjectPath()) + str_starts_with($path, Environment::getProjectPath() . '/') || PathUtility::isAllowedAdditionalPath($path) ); }
typo3/sysext/core/Tests/Functional/Utility/GeneralUtilityTest.php+51 −0 added@@ -0,0 +1,51 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3\CMS\Core\Tests\Functional\Utility; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; +use TYPO3\CMS\Core\Core\Environment; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +final class GeneralUtilityTest extends FunctionalTestCase +{ + protected bool $initializeDatabase = false; + + public static function isAllowedAbsPathDataProvider(): iterable + { + yield '{{project-path}}' => ['{{project-path}}', true]; + yield '{{project-path}}/' => ['{{project-path}}/', true]; + yield '{{project-path}}/some-file.png' => ['{{project-path}}/', true]; + yield '{{project-path}}-other' => ['{{project-path}}-other', false]; + yield '{{project-path}}-other/' => ['{{project-path}}-other', false]; + yield '{{project-path}}-other/some-file.png' => ['{{project-path}}-other', false]; + } + + /** + * See `\TYPO3\CMS\Core\Tests\Unit\Utility\PathUtilityTest::allowedAdditionalPathsAreEvaluated` + * for the evaluation of `$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath']`. + */ + #[Test] + #[DataProvider('isAllowedAbsPathDataProvider')] + public function allowedAbsolutePathIsEvaluated(string $path, bool $expectation): void + { + $path = str_replace('{{project-path}}', Environment::getPublicPath(), $path); + self::assertSame($expectation, GeneralUtility::isAllowedAbsPath($path)); + } +}
typo3/sysext/core/Tests/Unit/Utility/PathUtilityTest.php+4 −0 modified@@ -516,15 +516,19 @@ public static function allowedAdditionalPathsAreEvaluatedDataProvider(): \Genera 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/file.png', true]; + yield ['/var/shared', '/var/shared-secret', false]; 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/file.png', true]; + yield [['/var/shared'], '/var/shared-secret', false]; yield [['/var/shared/'], '/var/shared-secret', false]; yield [['/var/shared/'], '/var', false]; }
typo3/sysext/scheduler/Tests/Functional/Controller/NewSchedulerTaskControllerTest.php+1 −1 modified@@ -173,7 +173,7 @@ public function queryParamsAreProcessed(): void 'SCRIPT_NAME' => '/typo3/index.php', ])); $request = $request->withQueryParams([ - 'returnUrl' => Environment::getPublicPath() . 'typo3/scheduler/manage?token=123&test=value', + 'returnUrl' => Environment::getPublicPath() . '/typo3/scheduler/manage?token=123&test=value', 'defaultValues' => $defaultValues, ]);
150a983a5d68[SECURITY] Fix path prefix confusion in isAllowedAbsPath
3 files changed · +58 −2
typo3/sysext/core/Classes/Utility/GeneralUtility.php+3 −2 modified@@ -2541,10 +2541,11 @@ public static function isAllowedAbsPath(string $path): bool if (substr($path, 0, 6) === 'vfs://') { return true; } + $path = PathUtility::sanitizeTrailingSeparator($path); return PathUtility::isAbsolutePath($path) && static::validPathStr($path) && ( - str_starts_with($path, Environment::getProjectPath()) - || str_starts_with($path, Environment::getPublicPath()) + str_starts_with($path, Environment::getProjectPath() . '/') + || str_starts_with($path, Environment::getPublicPath() . '/') || PathUtility::isAllowedAdditionalPath($path) ); }
typo3/sysext/core/Tests/Functional/Utility/GeneralUtilityTest.php+51 −0 added@@ -0,0 +1,51 @@ +<?php + +declare(strict_types=1); + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +namespace TYPO3\CMS\Core\Tests\Functional\Utility; + +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Test; +use TYPO3\CMS\Core\Core\Environment; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; + +final class GeneralUtilityTest extends FunctionalTestCase +{ + protected bool $initializeDatabase = false; + + public static function isAllowedAbsPathDataProvider(): iterable + { + yield '{{project-path}}' => ['{{project-path}}', true]; + yield '{{project-path}}/' => ['{{project-path}}/', true]; + yield '{{project-path}}/some-file.png' => ['{{project-path}}/', true]; + yield '{{project-path}}-other' => ['{{project-path}}-other', false]; + yield '{{project-path}}-other/' => ['{{project-path}}-other', false]; + yield '{{project-path}}-other/some-file.png' => ['{{project-path}}-other', false]; + } + + /** + * See `\TYPO3\CMS\Core\Tests\Unit\Utility\PathUtilityTest::allowedAdditionalPathsAreEvaluated` + * for the evaluation of `$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath']`. + */ + #[Test] + #[DataProvider('isAllowedAbsPathDataProvider')] + public function allowedAbsolutePathIsEvaluated(string $path, bool $expectation): void + { + $path = str_replace('{{project-path}}', Environment::getPublicPath(), $path); + self::assertSame($expectation, GeneralUtility::isAllowedAbsPath($path)); + } +}
typo3/sysext/core/Tests/Unit/Utility/PathUtilityTest.php+4 −0 modified@@ -565,15 +565,19 @@ public static function allowedAdditionalPathsAreEvaluatedDataProvider(): \Genera 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/file.png', true]; + yield ['/var/shared', '/var/shared-secret', false]; 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/file.png', true]; + yield [['/var/shared'], '/var/shared-secret', false]; yield [['/var/shared/'], '/var/shared-secret', false]; yield [['/var/shared/'], '/var', false]; }
Vulnerability mechanics
Root cause
"The path allowance check in GeneralUtility::isAllowedAbsPath() performed a plain string prefix comparison without requiring a directory separator boundary."
Attack vector
Administrator users with access to the File Abstraction Layer could create new file storage definitions. By providing a path that superficially matched the project root but extended beyond it, such as `/var/www/html-other/secret.yaml` when the project root was `/var/www/html`, they could bypass the path check [ref_id=1]. This allowed them to point storage definitions to directories outside the intended project root, leading to broken access control [ref_id=1].
Affected code
The vulnerability resides in the `GeneralUtility::isAllowedAbsPath()` method within the TYPO3 CMS Core API. The issue stems from a string prefix comparison that did not account for directory boundaries, allowing paths like `/var/www/html-other/secret.yaml` to be considered valid when the project root was `/var/www/html` [ref_id=1].
What the fix does
The patch modifies the `isAllowedAbsPath()` function to correctly enforce directory separator boundaries during path prefix comparisons [patch_id=5349011, patch_id=5349010]. This prevents paths that merely share a prefix with the project root but extend beyond it from being incorrectly accepted. For example, it now correctly rejects `/var/shared-secret` when the allowed path is `/var/shared` [ref_id=2, ref_id=3].
Preconditions
- authThe attacker must be an administrator user with access to the File Abstraction Layer.
- configThe attacker must be able to create new file storage definitions.
Generated on Jun 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
3News mentions
1- TYPO3 CMS: Thirteen Backend Vulnerabilities Disclosed on June 9, 2026Vypr Intelligence · Jun 9, 2026