Skip to content

Commit 44c2fa9

Browse files
committed
[SECURITY] Fix path prefix confusion in isAllowedAbsPath
A path like `/var/www/html-other/file.yaml` was incorrectly accepted as allowed when the project root was `/var/www/html`, because the prefix check lacked a directory separator boundary. This allowed references to files outside the project root whenever an adjacent directory shared the project path as a string prefix. Resolves: #109844 Releases: main, 14.3, 13.4 Change-Id: I6ee31150c95cb943305fc95e06b82710dab1ee71 Security-Bulletin: TYPO3-CORE-SA-2026-016 Security-References: CVE-2026-49738 Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/94425 Reviewed-by: Oliver Hader <oliver.hader@typo3.org> Tested-by: Oliver Hader <oliver.hader@typo3.org>
1 parent bfe7c35 commit 44c2fa9

4 files changed

Lines changed: 58 additions & 2 deletions

File tree

typo3/sysext/core/Classes/Utility/GeneralUtility.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2071,9 +2071,10 @@ public static function validPathStr(string $theFile): bool
20712071
*/
20722072
public static function isAllowedAbsPath(string $path): bool
20732073
{
2074+
$path = PathUtility::sanitizeTrailingSeparator($path);
20742075
return PathUtility::isAbsolutePath($path) && static::validPathStr($path)
20752076
&& (
2076-
str_starts_with($path, Environment::getProjectPath())
2077+
str_starts_with($path, Environment::getProjectPath() . '/')
20772078
|| PathUtility::isAllowedAdditionalPath($path)
20782079
);
20792080
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the TYPO3 CMS project.
7+
*
8+
* It is free software; you can redistribute it and/or modify it under
9+
* the terms of the GNU General Public License, either version 2
10+
* of the License, or any later version.
11+
*
12+
* For the full copyright and license information, please read the
13+
* LICENSE.txt file that was distributed with this source code.
14+
*
15+
* The TYPO3 project - inspiring people to share!
16+
*/
17+
18+
namespace TYPO3\CMS\Core\Tests\Functional\Utility;
19+
20+
use PHPUnit\Framework\Attributes\DataProvider;
21+
use PHPUnit\Framework\Attributes\Test;
22+
use TYPO3\CMS\Core\Core\Environment;
23+
use TYPO3\CMS\Core\Utility\GeneralUtility;
24+
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
25+
26+
final class GeneralUtilityTest extends FunctionalTestCase
27+
{
28+
protected bool $initializeDatabase = false;
29+
30+
public static function isAllowedAbsPathDataProvider(): iterable
31+
{
32+
yield '{{project-path}}' => ['{{project-path}}', true];
33+
yield '{{project-path}}/' => ['{{project-path}}/', true];
34+
yield '{{project-path}}/some-file.png' => ['{{project-path}}/', true];
35+
yield '{{project-path}}-other' => ['{{project-path}}-other', false];
36+
yield '{{project-path}}-other/' => ['{{project-path}}-other', false];
37+
yield '{{project-path}}-other/some-file.png' => ['{{project-path}}-other', false];
38+
}
39+
40+
/**
41+
* See `\TYPO3\CMS\Core\Tests\Unit\Utility\PathUtilityTest::allowedAdditionalPathsAreEvaluated`
42+
* for the evaluation of `$GLOBALS['TYPO3_CONF_VARS']['BE']['lockRootPath']`.
43+
*/
44+
#[Test]
45+
#[DataProvider('isAllowedAbsPathDataProvider')]
46+
public function allowedAbsolutePathIsEvaluated(string $path, bool $expectation): void
47+
{
48+
$path = str_replace('{{project-path}}', Environment::getPublicPath(), $path);
49+
self::assertSame($expectation, GeneralUtility::isAllowedAbsPath($path));
50+
}
51+
}

typo3/sysext/core/Tests/Unit/Utility/PathUtilityTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,15 +516,19 @@ public static function allowedAdditionalPathsAreEvaluatedDataProvider(): \Genera
516516
yield ['/var/shared/', '/var/shared', true];
517517
yield ['/var/shared', '/var/shared/', true];
518518
yield ['/var/shared/', '/var/shared/', true];
519+
yield ['/var/shared', '/var/shared/file.png', true];
519520
yield ['/var/shared/', '/var/shared/file.png', true];
521+
yield ['/var/shared', '/var/shared-secret', false];
520522
yield ['/var/shared/', '/var/shared-secret', false];
521523
yield ['/var/shared/', '/var', false];
522524
// array settings
523525
yield [['/var'], '/var/shared', true];
524526
yield [['/var/shared/'], '/var/shared', true];
525527
yield [['/var/shared'], '/var/shared/', true];
526528
yield [['/var/shared/'], '/var/shared/', true];
529+
yield [['/var/shared'], '/var/shared/file.png', true];
527530
yield [['/var/shared/'], '/var/shared/file.png', true];
531+
yield [['/var/shared'], '/var/shared-secret', false];
528532
yield [['/var/shared/'], '/var/shared-secret', false];
529533
yield [['/var/shared/'], '/var', false];
530534
}

typo3/sysext/scheduler/Tests/Functional/Controller/NewSchedulerTaskControllerTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ public function queryParamsAreProcessed(): void
173173
'SCRIPT_NAME' => '/typo3/index.php',
174174
]));
175175
$request = $request->withQueryParams([
176-
'returnUrl' => Environment::getPublicPath() . 'typo3/scheduler/manage?token=123&test=value',
176+
'returnUrl' => Environment::getPublicPath() . '/typo3/scheduler/manage?token=123&test=value',
177177
'defaultValues' => $defaultValues,
178178
]);
179179

0 commit comments

Comments
 (0)