Skip to content

Commit 150a983

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/+/94423 Reviewed-by: Oliver Hader <oliver.hader@typo3.org> Tested-by: Oliver Hader <oliver.hader@typo3.org>
1 parent 17a3b78 commit 150a983

3 files changed

Lines changed: 58 additions & 2 deletions

File tree

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2541,10 +2541,11 @@ public static function isAllowedAbsPath(string $path): bool
25412541
if (substr($path, 0, 6) === 'vfs://') {
25422542
return true;
25432543
}
2544+
$path = PathUtility::sanitizeTrailingSeparator($path);
25442545
return PathUtility::isAbsolutePath($path) && static::validPathStr($path)
25452546
&& (
2546-
str_starts_with($path, Environment::getProjectPath())
2547-
|| str_starts_with($path, Environment::getPublicPath())
2547+
str_starts_with($path, Environment::getProjectPath() . '/')
2548+
|| str_starts_with($path, Environment::getPublicPath() . '/')
25482549
|| PathUtility::isAllowedAdditionalPath($path)
25492550
);
25502551
}
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
@@ -565,15 +565,19 @@ public static function allowedAdditionalPathsAreEvaluatedDataProvider(): \Genera
565565
yield ['/var/shared/', '/var/shared', true];
566566
yield ['/var/shared', '/var/shared/', true];
567567
yield ['/var/shared/', '/var/shared/', true];
568+
yield ['/var/shared', '/var/shared/file.png', true];
568569
yield ['/var/shared/', '/var/shared/file.png', true];
570+
yield ['/var/shared', '/var/shared-secret', false];
569571
yield ['/var/shared/', '/var/shared-secret', false];
570572
yield ['/var/shared/', '/var', false];
571573
// array settings
572574
yield [['/var'], '/var/shared', true];
573575
yield [['/var/shared/'], '/var/shared', true];
574576
yield [['/var/shared'], '/var/shared/', true];
575577
yield [['/var/shared/'], '/var/shared/', true];
578+
yield [['/var/shared'], '/var/shared/file.png', true];
576579
yield [['/var/shared/'], '/var/shared/file.png', true];
580+
yield [['/var/shared'], '/var/shared-secret', false];
577581
yield [['/var/shared/'], '/var/shared-secret', false];
578582
yield [['/var/shared/'], '/var', false];
579583
}

0 commit comments

Comments
 (0)