From e3d0d14a137f9a07a5742f82e77b651b34879bce Mon Sep 17 00:00:00 2001 From: Benni Mack <benni@typo3.org> Date: Fri, 10 Jan 2020 15:22:55 +0100 Subject: [PATCH] [TASK] Move VerifyDenyPattern functionality into separate logic This change targets a couple of things: - The global constant "FILE_DENY_PATTERN_DEFAULT" is moved to a class constant - The global constant "PHP_EXTENSIONS_DEFAULT" which is not in use anymore, is removed. - The security aspect of checking against the fileDenyPattern is extracted into its own Class where - The fileDenyPattern can never be empty, but only be set via DefaultConfiguration. This makes it easier to test this functionality, a single object is taking over the responsibility, and the logic is now in one place. Also, the non-usage of the global constant makes life easier. Resolves: #90147 Releases: master Change-Id: I9db0d6fc3b10f75a3735017cb9ac0d9bfd4ff02b Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/62843 Tested-by: Oliver Hader <oliver.hader@typo3.org> Tested-by: TYPO3com <noreply@typo3.com> Tested-by: Georg Ringer <georg.ringer@gmail.com> Reviewed-by: Oliver Hader <oliver.hader@typo3.org> Reviewed-by: Georg Ringer <georg.ringer@gmail.com> --- .../Classes/Core/SystemEnvironmentBuilder.php | 15 +- .../core/Classes/Resource/ResourceStorage.php | 3 +- .../Resource/Security/FileNameValidator.php | 86 ++++++ .../TypoScript/Parser/TypoScriptParser.php | 7 +- .../core/Classes/Utility/GeneralUtility.php | 9 +- .../Configuration/DefaultConfiguration.php | 1 - ...ecation-90147-UnifiedFileNameValidator.rst | 60 +++++ .../Security/FileNameValidatorTest.php | 255 ++++++++++++++++++ .../Tests/Unit/Utility/GeneralUtilityTest.php | 131 --------- .../Utility/GeneralUtilityTest.php | 131 +++++++++ .../File/CreateFolderController.php | 6 +- .../UploadedFileReferenceConverter.php | 3 +- typo3/sysext/impexp/Classes/Import.php | 3 +- typo3/sysext/impexp/Classes/ImportExport.php | 3 +- .../ExtensionScanner/Php/ConstantMatcher.php | 10 + .../Php/MethodCallStaticMatcher.php | 7 + .../Classes/View/FolderUtilityRenderer.php | 6 +- .../Classes/Report/Status/SecurityStatus.php | 14 +- 18 files changed, 590 insertions(+), 160 deletions(-) create mode 100644 typo3/sysext/core/Classes/Resource/Security/FileNameValidator.php create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Deprecation-90147-UnifiedFileNameValidator.rst create mode 100644 typo3/sysext/core/Tests/Unit/Resource/Security/FileNameValidatorTest.php diff --git a/typo3/sysext/core/Classes/Core/SystemEnvironmentBuilder.php b/typo3/sysext/core/Classes/Core/SystemEnvironmentBuilder.php index b9096d18f7e6..18402f0f63ca 100644 --- a/typo3/sysext/core/Classes/Core/SystemEnvironmentBuilder.php +++ b/typo3/sysext/core/Classes/Core/SystemEnvironmentBuilder.php @@ -14,6 +14,7 @@ namespace TYPO3\CMS\Core\Core; * The TYPO3 project - inspiring people to share! */ +use TYPO3\CMS\Core\Resource\Security\FileNameValidator; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\PathUtility; @@ -92,6 +93,7 @@ class SystemEnvironmentBuilder { // Check one of the constants and return early if already defined, // needed if multiple requests are handled in one process, for instance in functional testing. + // This check can be removed in TYPO3 v11.0. if (defined('FILE_DENY_PATTERN_DEFAULT')) { return; } @@ -101,16 +103,19 @@ class SystemEnvironmentBuilder defined('CR') ?: define('CR', chr(13)); defined('CRLF') ?: define('CRLF', CR . LF); - // Security related constant: Default value of fileDenyPattern - define('FILE_DENY_PATTERN_DEFAULT', '\\.(php[3-8]?|phpsh|phtml|pht|phar|shtml|cgi)(\\..*)?$|\\.pl$|^\\.htaccess$'); - // Security related constant: List of file extensions that should be registered as php script file extensions - define('PHP_EXTENSIONS_DEFAULT', 'php,php3,php4,php5,php6,php7,php8,phpsh,inc,phtml,pht,phar'); - // Relative path from document root to typo3/ directory, hardcoded to "typo3/" if (!defined('TYPO3_mainDir')) { define('TYPO3_mainDir', 'typo3/'); } + /** + * @deprecated use FileNameAccess class to retrieve this information, will be removed in TYPO3 v11.0 + */ + define('FILE_DENY_PATTERN_DEFAULT', FileNameValidator::DEFAULT_FILE_DENY_PATTERN); + /** + * @deprecated use FILE_DENY_PATTERN and FileNameAccess class to retrieve this information, will be removed in TYPO3 v11.0 + */ + define('PHP_EXTENSIONS_DEFAULT', 'php,php3,php4,php5,php6,php7,php8,phpsh,inc,phtml,pht,phar'); /** * @deprecated use Typo3Information class to retrieve this information, will be removed in TYPO3 v11.0 */ diff --git a/typo3/sysext/core/Classes/Resource/ResourceStorage.php b/typo3/sysext/core/Classes/Resource/ResourceStorage.php index 3d8c85d6e5fe..cb5bafb0f7e8 100644 --- a/typo3/sysext/core/Classes/Resource/ResourceStorage.php +++ b/typo3/sysext/core/Classes/Resource/ResourceStorage.php @@ -61,6 +61,7 @@ use TYPO3\CMS\Core\Resource\Search\Result\DriverFilteredSearchResult; use TYPO3\CMS\Core\Resource\Search\Result\EmptyFileSearchResult; use TYPO3\CMS\Core\Resource\Search\Result\FileSearchResult; use TYPO3\CMS\Core\Resource\Search\Result\FileSearchResultInterface; +use TYPO3\CMS\Core\Resource\Security\FileNameValidator; use TYPO3\CMS\Core\Utility\Exception\NotImplementedMethodException; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\PathUtility; @@ -832,7 +833,7 @@ class ResourceStorage implements ResourceStorageInterface protected function checkFileExtensionPermission($fileName) { $fileName = $this->driver->sanitizeFileName($fileName); - return GeneralUtility::verifyFilenameAgainstDenyPattern($fileName); + return GeneralUtility::makeInstance(FileNameValidator::class)->isValid($fileName); } /** diff --git a/typo3/sysext/core/Classes/Resource/Security/FileNameValidator.php b/typo3/sysext/core/Classes/Resource/Security/FileNameValidator.php new file mode 100644 index 000000000000..7e5bf3083856 --- /dev/null +++ b/typo3/sysext/core/Classes/Resource/Security/FileNameValidator.php @@ -0,0 +1,86 @@ +<?php +declare(strict_types = 1); +namespace TYPO3\CMS\Core\Resource\Security; + +/* + * 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! + */ + +/** + * Ensures that any filename that an editor chooses for naming (or uses for uploading a file) is valid, meaning + * that no invalid characters (null-bytes) are added, or that the file does not contain an invalid file extension. + */ +class FileNameValidator +{ + /** + * Previously this was used within SystemEnvironmentBuilder + */ + public const DEFAULT_FILE_DENY_PATTERN = '\\.(php[3-8]?|phpsh|phtml|pht|phar|shtml|cgi)(\\..*)?$|\\.pl$|^\\.htaccess$'; + + /** + * @var string + */ + protected $fileDenyPattern; + + public function __construct(string $fileDenyPattern = null) + { + if ($fileDenyPattern !== null) { + $this->fileDenyPattern = $fileDenyPattern; + } elseif (isset($GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'])) { + $this->fileDenyPattern = (string)$GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern']; + } else { + $this->fileDenyPattern = static::DEFAULT_FILE_DENY_PATTERN; + } + } + + /** + * Verifies the input filename against the 'fileDenyPattern' + * + * Filenames are not allowed to contain control characters. Therefore we + * always filter on [[:cntrl:]]. + * + * @param string $fileName File path to evaluate + * @return bool Returns TRUE if the file name is OK. + */ + public function isValid(string $fileName): bool + { + $pattern = '/[[:cntrl:]]/'; + if ($fileName !== '' && $this->fileDenyPattern !== '') { + $pattern = '/(?:[[:cntrl:]]|' . $this->fileDenyPattern . ')/iu'; + } + return preg_match($pattern, $fileName) === 0; + } + + /** + * Find out if there is a custom file deny pattern configured. + * + * @return bool + */ + public function customFileDenyPatternConfigured(): bool + { + return $this->fileDenyPattern !== self::DEFAULT_FILE_DENY_PATTERN; + } + + /** + * Checks if the given file deny pattern does not have parts that the default pattern should + * recommend. Used in status overview. + * + * @return bool + */ + public function missingImportantPatterns(): bool + { + $defaultParts = explode('|', self::DEFAULT_FILE_DENY_PATTERN); + $givenParts = explode('|', $this->fileDenyPattern); + $missingParts = array_diff($defaultParts, $givenParts); + return !empty($missingParts); + } +} diff --git a/typo3/sysext/core/Classes/TypoScript/Parser/TypoScriptParser.php b/typo3/sysext/core/Classes/TypoScript/Parser/TypoScriptParser.php index b466df66b135..84328d665a7d 100644 --- a/typo3/sysext/core/Classes/TypoScript/Parser/TypoScriptParser.php +++ b/typo3/sysext/core/Classes/TypoScript/Parser/TypoScriptParser.php @@ -20,6 +20,7 @@ use TYPO3\CMS\Backend\Configuration\TypoScript\ConditionMatching\ConditionMatche use TYPO3\CMS\Core\Configuration\TypoScript\ConditionMatching\AbstractConditionMatcher; use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Log\LogManager; +use TYPO3\CMS\Core\Resource\Security\FileNameValidator; use TYPO3\CMS\Core\TimeTracker\TimeTracker; use TYPO3\CMS\Core\TypoScript\ExtendedTemplateService; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -1034,7 +1035,7 @@ class TypoScriptParser $readableFileName = rtrim($readableFilePrefix, '/') . '/' . $fileObject->getFilename(); $content .= LF . '### @import \'' . $readableFileName . '\' begin ###' . LF; // Check for allowed files - if (!GeneralUtility::verifyFilenameAgainstDenyPattern($fileObject->getFilename())) { + if (!GeneralUtility::makeInstance(FileNameValidator::class)->isValid($fileObject->getFilename())) { $content .= self::typoscriptIncludeError('File "' . $readableFileName . '" was not included since it is not allowed due to fileDenyPattern.'); } else { $includedFiles[] = $fileObject->getPathname(); @@ -1105,7 +1106,7 @@ class TypoScriptParser if ((string)$filename !== '') { // Must exist and must not contain '..' and must be relative // Check for allowed files - if (!GeneralUtility::verifyFilenameAgainstDenyPattern($absfilename)) { + if (!GeneralUtility::makeInstance(FileNameValidator::class)->isValid($absfilename)) { $newString .= self::typoscriptIncludeError('File "' . $filename . '" was not included since it is not allowed due to fileDenyPattern.'); } else { $fileExists = false; @@ -1294,7 +1295,7 @@ class TypoScriptParser if ($inIncludePart === 'FILE') { // Some file checks - if (!GeneralUtility::verifyFilenameAgainstDenyPattern($realFileName)) { + if (!GeneralUtility::makeInstance(FileNameValidator::class)->isValid($realFileName)) { throw new \UnexpectedValueException(sprintf('File "%s" was not included since it is not allowed due to fileDenyPattern.', $fileName), 1382651858); } if (empty($realFileName)) { diff --git a/typo3/sysext/core/Classes/Utility/GeneralUtility.php b/typo3/sysext/core/Classes/Utility/GeneralUtility.php index 3603d0f8657e..b61e43a10b5c 100644 --- a/typo3/sysext/core/Classes/Utility/GeneralUtility.php +++ b/typo3/sysext/core/Classes/Utility/GeneralUtility.php @@ -26,6 +26,7 @@ use TYPO3\CMS\Core\Core\ClassLoadingInformation; use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Http\RequestFactory; use TYPO3\CMS\Core\Log\LogManager; +use TYPO3\CMS\Core\Resource\Security\FileNameValidator; use TYPO3\CMS\Core\Service\OpcodeCacheService; use TYPO3\CMS\Core\SingletonInterface; @@ -3060,14 +3061,12 @@ class GeneralUtility * * @param string $filename File path to evaluate * @return bool + * @deprecated will be removed in TYPO3 v11.0. Use the new FileNameValidator API instead. */ public static function verifyFilenameAgainstDenyPattern($filename) { - $pattern = '/[[:cntrl:]]/'; - if ((string)$filename !== '' && (string)$GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] !== '') { - $pattern = '/(?:[[:cntrl:]]|' . $GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] . ')/iu'; - } - return preg_match($pattern, $filename) === 0; + trigger_error('GeneralUtility::verifyFilenameAgainstDenyPattern() will be removed in TYPO3 v11.0. Use FileNameValidator->isValid($filename) instead.', E_USER_DEPRECATED); + return self::makeInstance(FileNameValidator::class)->isValid((string)$filename); } /** diff --git a/typo3/sysext/core/Configuration/DefaultConfiguration.php b/typo3/sysext/core/Configuration/DefaultConfiguration.php index 02b12b12e7eb..27a861f8ecda 100644 --- a/typo3/sysext/core/Configuration/DefaultConfiguration.php +++ b/typo3/sysext/core/Configuration/DefaultConfiguration.php @@ -1277,7 +1277,6 @@ return [ 'defaultPermissions' => [], 'defaultUC' => [], 'customPermOptions' => [], // Array with sets of custom permission options. Syntax is; 'key' => array('header' => 'header string, language split', 'items' => array('key' => array('label, language split','icon reference', 'Description text, language split'))). Keys cannot contain ":|," characters. - 'fileDenyPattern' => FILE_DENY_PATTERN_DEFAULT, 'interfaces' => 'backend', 'explicitADmode' => 'explicitDeny', 'flexformForceCDATA' => 0, diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-90147-UnifiedFileNameValidator.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-90147-UnifiedFileNameValidator.rst new file mode 100644 index 000000000000..9d4233f1fbdb --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-90147-UnifiedFileNameValidator.rst @@ -0,0 +1,60 @@ +.. include:: ../../Includes.txt + +================================================= +Deprecation: #90147 - Unified File Name Validator +================================================= + +See :issue:`90147` + +Description +=========== + +The logic for validating if a new (uploaded) or renamed file's name is allowed, is now available in an encapsulated PHP class :php:`FileNameValidator`. + +The functionality is moved so all logic is encapsulated in one single place: +- PHP constant `FILE_DENY_PATTERN_DEFAULT` is migrated into a class constant. +- LocalConfiguration setting is only used when it differs from the default. +- The GeneralUtility method is deprecated and calls `FileNameValidator->isValid()` directly. + +This optimization helps to only utilize and use PHP's memory if +needed, and avoids to define run-time constants or variables, +but only initializes the logic when needed - e.g. when uploading files or using TYPO3's importer via EXT:impexp. + +In addition, the PHP constant :php:`PHP_EXTENSIONS_DEFAULT` which is not +in use anymore, is marked as deprecated as well. + + +Impact +====== + +Using the method :php:`GeneralUtility::verifyFilenameAgainstDenyPattern()` directly will trigger a deprecation message. + +Using the constants will continue to work but will be removed TYPO3 v11.0. + +The system-wide setting to override the default file deny pattern, called :php:`$GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern']` is only set when different from the systems' default. If it is the same, the option is not set anymore by TYPO3 Core. + + +Affected Installations +====================== + +TYPO3 installations with PHP code calling the mentioned method directly or using one of the global constants directly. + + +Migration +========= + +Instead of calling + +:php:`GeneralUtility::verifyFilenameAgainstDenyPattern($filename)` + +use + +:php:`GeneralUtility::makeInstance(FileNameValidator::class)->isValid($filename);` + +Instead of using the constant :php:`FILE_DENY_PATTERN_DEFAULT` use :php:`FileNameValidator::DEFAULT_FILE_DENY_PATTERN`. + +For the PHP constant :php:`PHP_EXTENSIONS_DEFAULT` there is no replacement, as it has no benefit for TYPO3 Core anymore. + +The extension scanner will detect the method calls or the usage of the constants. + +.. index:: LocalConfiguration, PHP-API, FullyScanned, ext:core \ No newline at end of file diff --git a/typo3/sysext/core/Tests/Unit/Resource/Security/FileNameValidatorTest.php b/typo3/sysext/core/Tests/Unit/Resource/Security/FileNameValidatorTest.php new file mode 100644 index 000000000000..b7c110668010 --- /dev/null +++ b/typo3/sysext/core/Tests/Unit/Resource/Security/FileNameValidatorTest.php @@ -0,0 +1,255 @@ +<?php +declare(strict_types = 1); + +namespace TYPO3\CMS\Core\Tests\Unit\Resource\Security; + +/* + * 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! + */ + +use PHPUnit\Framework\TestCase; +use TYPO3\CMS\Core\Resource\Security\FileNameValidator; +use TYPO3\CMS\Core\Utility\StringUtility; + +class FileNameValidatorTest extends TestCase +{ + /** + * @return array + */ + public function deniedFilesWithoutDenyPatternDataProvider(): array + { + return [ + 'Nul character in file' => ['image' . "\0" . '.gif'], + 'Nul character in file with .php' => ['image.php' . "\0" . '.gif'], + 'Nul character and UTF-8 in file' => ['СÑылка' . "\0" . '.gif'], + 'Nul character and Latin-1 in file' => ['ÉÃØ' . "\0" . '.gif'], + ]; + } + + /** + * Tests whether validator detects files with nul character without file deny pattern. + * + * @param string $deniedFile + * @test + * @dataProvider deniedFilesWithoutDenyPatternDataProvider + */ + public function verifyNulCharacterFilesAgainstPatternWithoutFileDenyPattern(string $deniedFile): void + { + $subject = new FileNameValidator(''); + self::assertFalse($subject->isValid($deniedFile)); + + $GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] = ''; + $subject = new FileNameValidator(); + self::assertFalse($subject->isValid($deniedFile)); + } + + /** + * @return array + */ + public function deniedFilesWithDefaultDenyPatternDataProvider(): array + { + $data = [ + 'Nul character in file' => ['image' . "\0", '.gif'], + 'Nul character in file with .php' => ['image.php' . "\0", '.gif'], + 'Nul character and UTF-8 in file' => ['СÑылка' . "\0", '.gif'], + 'Nul character and Latin-1 in file' => ['ÉÃØ' . "\0", '.gif'], + 'Lower umlaut .php file' => ['üWithFile', '.php'], + 'Upper umlaut .php file' => ['fileWithÃœ', '.php'], + 'invalid UTF-8-sequence' => ["\xc0" . 'file', '.php'], + 'Could be overlong NUL in some UTF-8 implementations, invalid in RFC3629' => ["\xc0\x80" . 'file', '.php'], + 'Regular .php file' => ['file', '.php'], + 'Regular .php3 file' => ['file', '.php3'], + 'Regular .php5 file' => ['file', '.php5'], + 'Regular .php7 file' => ['file', '.php7'], + 'Regular .phpsh file' => ['file', '.phpsh'], + 'Regular .phtml file' => ['file', '.phtml'], + 'Regular .pht file' => ['file', '.pht'], + 'Regular .phar file' => ['file', '.phar'], + 'Regular .shtml file' => ['file', '.shtml'], + 'Regular .cgi file' => ['file', '.cgi'], + 'Regular .pl file' => ['file', '.pl'], + 'Wrapped .php file ' => ['file', '.php.txt'], + 'Wrapped .php3 file' => ['file', '.php3.txt'], + 'Wrapped .php5 file' => ['file', '.php5.txt'], + 'Wrapped .php7 file' => ['file', '.php7.txt'], + 'Wrapped .phpsh file' => ['file', '.phpsh.txt'], + 'Wrapped .phtml file' => ['file', '.phtml.txt'], + 'Wrapped .pht file' => ['file', '.pht.txt'], + 'Wrapped .phar file' => ['file', '.phar.txt'], + 'Wrapped .shtml file' => ['file', '.shtml.txt'], + 'Wrapped .cgi file' => ['file', '.cgi.txt'], + // allowed "Wrapped .pl file" in order to allow language specific files containing ".pl." + '.htaccess file' => ['', '.htaccess'], + ]; + + // Mixing with regular utf-8 + $utf8Characters = 'СÑылка'; + foreach ($data as $key => $value) { + if ($value[0] === '') { + continue; + } + $data[$key . ' with UTF-8 characters prepended'] = [$utf8Characters . $value[0], $value[1]]; + $data[$key . ' with UTF-8 characters appended'] = [$value[0] . $utf8Characters, $value[1]]; + } + + // combine to single value + $data = array_map( + function (array $values): array { + return [implode('', $values)]; + }, + $data + ); + + // Encoding with UTF-16 + foreach ($data as $key => $value) { + $data[$key . ' encoded with UTF-16'] = [mb_convert_encoding($value[0], 'UTF-16')]; + } + + return $data; + } + + /** + * Tests whether the basic FILE_DENY_PATTERN detects denied files. + * + * @param string $deniedFile + * @test + * @dataProvider deniedFilesWithDefaultDenyPatternDataProvider + */ + public function isValidDetectsNotAllowedFiles(string $deniedFile): void + { + $subject = new FileNameValidator(); + self::assertFalse($subject->isValid($deniedFile)); + } + + /** + * @return array + */ + public function insecureFilesDataProvider(): array + { + return [ + 'Classic php file' => ['user.php'], + 'A random .htaccess file' => ['.htaccess'], + 'Wrapped .php file' => ['file.php.txt'], + ]; + } + + /** + * @param string $fileName + * @test + * @dataProvider insecureFilesDataProvider + */ + public function isValidAcceptsNotAllowedFilesDueToInsecureSetting(string $fileName): void + { + $GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] = '\\.phc$'; + $subject = new FileNameValidator(); + self::assertTrue($subject->isValid($fileName)); + } + + /** + * @return array + */ + public function allowedFilesDataProvider(): array + { + return [ + 'Regular .gif file' => ['image.gif'], + 'Regular uppercase .gif file' => ['IMAGE.gif'], + 'UTF-8 .gif file' => ['СÑылка.gif'], + 'Lower umlaut .jpg file' => ['üWithFile.jpg'], + 'Upper umlaut .png file' => ['fileWithÃœ.png'], + 'Latin-1 .gif file' => ['ÉÃØ.gif'], + 'Wrapped .pl file' => ['file.pl.txt'], + ]; + } + + /** + * Tests whether the basic file deny pattern accepts allowed files. + * + * @param string $allowedFile + * @test + * @dataProvider allowedFilesDataProvider + */ + public function isValidAcceptAllowedFiles(string $allowedFile): void + { + $subject = new FileNameValidator(); + self::assertTrue($subject->isValid($allowedFile)); + } + + /** + * @test + */ + public function isCustomDenyPatternConfigured(): void + { + $subject = new FileNameValidator('nothing-really'); + self::assertTrue($subject->customFileDenyPatternConfigured()); + $GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] = 'something-else'; + $subject = new FileNameValidator(); + self::assertTrue($subject->customFileDenyPatternConfigured()); + $GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] = FileNameValidator::DEFAULT_FILE_DENY_PATTERN; + $subject = new FileNameValidator(); + self::assertFalse($subject->customFileDenyPatternConfigured()); + $subject = new FileNameValidator(FileNameValidator::DEFAULT_FILE_DENY_PATTERN); + self::assertFalse($subject->customFileDenyPatternConfigured()); + } + + /** + * @test + */ + public function customFileDenyPatternFindsMissingImportantParts(): void + { + $subject = new FileNameValidator('\\.php$|.php8$'); + self::assertTrue($subject->missingImportantPatterns()); + $subject = new FileNameValidator(FileNameValidator::DEFAULT_FILE_DENY_PATTERN); + self::assertFalse($subject->missingImportantPatterns()); + } + + /** + * Data provider for 'defaultFileDenyPatternMatchesPhpExtension' test case. + * + * @return array + */ + public function phpExtensionDataProvider(): array + { + $data = []; + $fileName = StringUtility::getUniqueId('filename'); + $phpExtensions = ['php', 'php3', 'php4', 'php5', 'php7', 'phpsh', 'phtml', 'pht']; + foreach ($phpExtensions as $extension) { + $data[] = [$fileName . '.' . $extension]; + $data[] = [$fileName . '.' . $extension . '.txt']; + } + return $data; + } + + /** + * Tests whether an accordant PHP extension is denied. + * + * @test + * @dataProvider phpExtensionDataProvider + * @param string $fileName + */ + public function defaultFileDenyPatternMatchesPhpExtension(string $fileName): void + { + self::assertGreaterThan(0, preg_match('/' . FileNameValidator::DEFAULT_FILE_DENY_PATTERN . '/', $fileName), $fileName); + } + + /** + * Tests whether an accordant PHP extension is denied. + * + * @test + * @dataProvider phpExtensionDataProvider + * @param string $fileName + */ + public function invalidPhpExtensionIsDetected(string $fileName): void + { + $subject = new FileNameValidator(); + self::assertFalse($subject->isValid($fileName)); + } +} diff --git a/typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php b/typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php index fb68997d6650..73b7ea9a64dd 100644 --- a/typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php +++ b/typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php @@ -4005,137 +4005,6 @@ class GeneralUtilityTest extends UnitTestCase self::assertTrue(GeneralUtility::validPathStr($path)); } - /** - * @return array - */ - public function deniedFilesWithoutDenyPatternDataProvider(): array - { - return [ - 'Nul character in file' => ['image' . "\0" . '.gif'], - 'Nul character in file with .php' => ['image.php' . "\0" . '.gif'], - 'Nul character and UTF-8 in file' => ['СÑылка' . "\0" . '.gif'], - 'Nul character and Latin-1 in file' => ['ÉÃØ' . "\0" . '.gif'], - ]; - } - - /** - * Tests whether verifyFilenameAgainstDenyPattern detects files with nul character without file deny pattern. - * - * @param string $deniedFile - * @test - * @dataProvider deniedFilesWithoutDenyPatternDataProvider - */ - public function verifyNulCharacterFilesAgainstPatternWithoutFileDenyPattern(string $deniedFile) - { - $GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] = ''; - self::assertFalse(GeneralUtility::verifyFilenameAgainstDenyPattern($deniedFile)); - } - - /** - * @return array - */ - public function deniedFilesWithDefaultDenyPatternDataProvider(): array - { - $data = [ - 'Nul character in file' => ['image' . "\0", '.gif'], - 'Nul character in file with .php' => ['image.php' . "\0", '.gif'], - 'Nul character and UTF-8 in file' => ['СÑылка' . "\0", '.gif'], - 'Nul character and Latin-1 in file' => ['ÉÃØ' . "\0", '.gif'], - 'Lower umlaut .php file' => ['üWithFile', '.php'], - 'Upper umlaut .php file' => ['fileWithÃœ', '.php'], - 'invalid UTF-8-sequence' => ["\xc0" . 'file', '.php'], - 'Could be overlong NUL in some UTF-8 implementations, invalid in RFC3629' => ["\xc0\x80" . 'file', '.php'], - 'Regular .php file' => ['file' , '.php'], - 'Regular .php3 file' => ['file', '.php3'], - 'Regular .php5 file' => ['file', '.php5'], - 'Regular .php7 file' => ['file', '.php7'], - 'Regular .phpsh file' => ['file', '.phpsh'], - 'Regular .phtml file' => ['file', '.phtml'], - 'Regular .pht file' => ['file', '.pht'], - 'Regular .phar file' => ['file', '.phar'], - 'Regular .shtml file' => ['file', '.shtml'], - 'Regular .cgi file' => ['file', '.cgi'], - 'Regular .pl file' => ['file', '.pl'], - 'Wrapped .php file ' => ['file', '.php.txt'], - 'Wrapped .php3 file' => ['file', '.php3.txt'], - 'Wrapped .php5 file' => ['file', '.php5.txt'], - 'Wrapped .php7 file' => ['file', '.php7.txt'], - 'Wrapped .phpsh file' => ['file', '.phpsh.txt'], - 'Wrapped .phtml file' => ['file', '.phtml.txt'], - 'Wrapped .pht file' => ['file', '.pht.txt'], - 'Wrapped .phar file' => ['file', '.phar.txt'], - 'Wrapped .shtml file' => ['file', '.shtml.txt'], - 'Wrapped .cgi file' => ['file', '.cgi.txt'], - // allowed "Wrapped .pl file" in order to allow language specific files containing ".pl." - '.htaccess file' => ['', '.htaccess'], - ]; - - // Mixing with regular utf-8 - $utf8Characters = 'СÑылка'; - foreach ($data as $key => $value) { - if ($value[0] === '') { - continue; - } - $data[$key . ' with UTF-8 characters prepended'] = [$utf8Characters . $value[0], $value[1]]; - $data[$key . ' with UTF-8 characters appended'] = [$value[0] . $utf8Characters, $value[1]]; - } - - // combine to single value - $data = array_map( - function (array $values): array { - return [implode('', $values)]; - }, - $data - ); - - // Encoding with UTF-16 - foreach ($data as $key => $value) { - $data[$key . ' encoded with UTF-16'] = [mb_convert_encoding($value[0], 'UTF-16')]; - } - - return $data; - } - - /** - * Tests whether verifyFilenameAgainstDenyPattern detects denied files. - * - * @param string $deniedFile - * @test - * @dataProvider deniedFilesWithDefaultDenyPatternDataProvider - */ - public function verifyFilenameAgainstDenyPatternDetectsNotAllowedFiles($deniedFile) - { - self::assertFalse(GeneralUtility::verifyFilenameAgainstDenyPattern($deniedFile)); - } - - /** - * @return array - */ - public function allowedFilesDataProvider(): array - { - return [ - 'Regular .gif file' => ['image.gif'], - 'Regular uppercase .gif file' => ['IMAGE.gif'], - 'UTF-8 .gif file' => ['СÑылка.gif'], - 'Lower umlaut .jpg file' => ['üWithFile.jpg'], - 'Upper umlaut .png file' => ['fileWithÃœ.png'], - 'Latin-1 .gif file' => ['ÉÃØ.gif'], - 'Wrapped .pl file' => ['file.pl.txt'], - ]; - } - - /** - * Tests whether verifyFilenameAgainstDenyPattern accepts allowed files. - * - * @param string $allowedFile - * @test - * @dataProvider allowedFilesDataProvider - */ - public function verifyFilenameAgainstDenyPatternAcceptAllowedFiles(string $allowedFile) - { - self::assertTrue(GeneralUtility::verifyFilenameAgainstDenyPattern($allowedFile)); - } - ///////////////////////////////////////////////////////////////////////////////////// // Tests concerning copyDirectory ///////////////////////////////////////////////////////////////////////////////////// diff --git a/typo3/sysext/core/Tests/UnitDeprecated/Utility/GeneralUtilityTest.php b/typo3/sysext/core/Tests/UnitDeprecated/Utility/GeneralUtilityTest.php index cf89a3c5138c..9477411b52c0 100644 --- a/typo3/sysext/core/Tests/UnitDeprecated/Utility/GeneralUtilityTest.php +++ b/typo3/sysext/core/Tests/UnitDeprecated/Utility/GeneralUtilityTest.php @@ -86,4 +86,135 @@ class GeneralUtilityTest extends UnitTestCase ] ]; } + + /** + * @return array + */ + public function deniedFilesWithoutDenyPatternDataProvider(): array + { + return [ + 'Nul character in file' => ['image' . "\0" . '.gif'], + 'Nul character in file with .php' => ['image.php' . "\0" . '.gif'], + 'Nul character and UTF-8 in file' => ['СÑылка' . "\0" . '.gif'], + 'Nul character and Latin-1 in file' => ['ÉÃØ' . "\0" . '.gif'], + ]; + } + + /** + * Tests whether verifyFilenameAgainstDenyPattern detects files with nul character without file deny pattern. + * + * @param string $deniedFile + * @test + * @dataProvider deniedFilesWithoutDenyPatternDataProvider + */ + public function verifyNulCharacterFilesAgainstPatternWithoutFileDenyPattern(string $deniedFile) + { + $GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] = ''; + self::assertFalse(GeneralUtility::verifyFilenameAgainstDenyPattern($deniedFile)); + } + + /** + * @return array + */ + public function deniedFilesWithDefaultDenyPatternDataProvider(): array + { + $data = [ + 'Nul character in file' => ['image' . "\0", '.gif'], + 'Nul character in file with .php' => ['image.php' . "\0", '.gif'], + 'Nul character and UTF-8 in file' => ['СÑылка' . "\0", '.gif'], + 'Nul character and Latin-1 in file' => ['ÉÃØ' . "\0", '.gif'], + 'Lower umlaut .php file' => ['üWithFile', '.php'], + 'Upper umlaut .php file' => ['fileWithÃœ', '.php'], + 'invalid UTF-8-sequence' => ["\xc0" . 'file', '.php'], + 'Could be overlong NUL in some UTF-8 implementations, invalid in RFC3629' => ["\xc0\x80" . 'file', '.php'], + 'Regular .php file' => ['file' , '.php'], + 'Regular .php3 file' => ['file', '.php3'], + 'Regular .php5 file' => ['file', '.php5'], + 'Regular .php7 file' => ['file', '.php7'], + 'Regular .phpsh file' => ['file', '.phpsh'], + 'Regular .phtml file' => ['file', '.phtml'], + 'Regular .pht file' => ['file', '.pht'], + 'Regular .phar file' => ['file', '.phar'], + 'Regular .shtml file' => ['file', '.shtml'], + 'Regular .cgi file' => ['file', '.cgi'], + 'Regular .pl file' => ['file', '.pl'], + 'Wrapped .php file ' => ['file', '.php.txt'], + 'Wrapped .php3 file' => ['file', '.php3.txt'], + 'Wrapped .php5 file' => ['file', '.php5.txt'], + 'Wrapped .php7 file' => ['file', '.php7.txt'], + 'Wrapped .phpsh file' => ['file', '.phpsh.txt'], + 'Wrapped .phtml file' => ['file', '.phtml.txt'], + 'Wrapped .pht file' => ['file', '.pht.txt'], + 'Wrapped .phar file' => ['file', '.phar.txt'], + 'Wrapped .shtml file' => ['file', '.shtml.txt'], + 'Wrapped .cgi file' => ['file', '.cgi.txt'], + // allowed "Wrapped .pl file" in order to allow language specific files containing ".pl." + '.htaccess file' => ['', '.htaccess'], + ]; + + // Mixing with regular utf-8 + $utf8Characters = 'СÑылка'; + foreach ($data as $key => $value) { + if ($value[0] === '') { + continue; + } + $data[$key . ' with UTF-8 characters prepended'] = [$utf8Characters . $value[0], $value[1]]; + $data[$key . ' with UTF-8 characters appended'] = [$value[0] . $utf8Characters, $value[1]]; + } + + // combine to single value + $data = array_map( + function (array $values): array { + return [implode('', $values)]; + }, + $data + ); + + // Encoding with UTF-16 + foreach ($data as $key => $value) { + $data[$key . ' encoded with UTF-16'] = [mb_convert_encoding($value[0], 'UTF-16')]; + } + + return $data; + } + + /** + * Tests whether verifyFilenameAgainstDenyPattern detects denied files. + * + * @param string $deniedFile + * @test + * @dataProvider deniedFilesWithDefaultDenyPatternDataProvider + */ + public function verifyFilenameAgainstDenyPatternDetectsNotAllowedFiles($deniedFile) + { + self::assertFalse(GeneralUtility::verifyFilenameAgainstDenyPattern($deniedFile)); + } + + /** + * @return array + */ + public function allowedFilesDataProvider(): array + { + return [ + 'Regular .gif file' => ['image.gif'], + 'Regular uppercase .gif file' => ['IMAGE.gif'], + 'UTF-8 .gif file' => ['СÑылка.gif'], + 'Lower umlaut .jpg file' => ['üWithFile.jpg'], + 'Upper umlaut .png file' => ['fileWithÃœ.png'], + 'Latin-1 .gif file' => ['ÉÃØ.gif'], + 'Wrapped .pl file' => ['file.pl.txt'], + ]; + } + + /** + * Tests whether verifyFilenameAgainstDenyPattern accepts allowed files. + * + * @param string $allowedFile + * @test + * @dataProvider allowedFilesDataProvider + */ + public function verifyFilenameAgainstDenyPatternAcceptAllowedFiles(string $allowedFile) + { + self::assertTrue(GeneralUtility::verifyFilenameAgainstDenyPattern($allowedFile)); + } } diff --git a/typo3/sysext/filelist/Classes/Controller/File/CreateFolderController.php b/typo3/sysext/filelist/Classes/Controller/File/CreateFolderController.php index 5f38159838e2..8f8aca8a7283 100644 --- a/typo3/sysext/filelist/Classes/Controller/File/CreateFolderController.php +++ b/typo3/sysext/filelist/Classes/Controller/File/CreateFolderController.php @@ -26,6 +26,7 @@ use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException; use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry; use TYPO3\CMS\Core\Resource\ResourceFactory; +use TYPO3\CMS\Core\Resource\Security\FileNameValidator; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\Fluid\View\StandaloneView; @@ -208,8 +209,9 @@ class CreateFolderController // Create a list of allowed file extensions with the readable format "youtube, vimeo" etc. $fileExtList = []; $onlineMediaFileExt = OnlineMediaHelperRegistry::getInstance()->getSupportedFileExtensions(); + $fileNameVerifier = GeneralUtility::makeInstance(FileNameValidator::class); foreach ($onlineMediaFileExt as $fileExt) { - if (GeneralUtility::verifyFilenameAgainstDenyPattern('.' . $fileExt)) { + if ($fileNameVerifier->isValid('.' . $fileExt)) { $fileExtList[] = strtoupper(htmlspecialchars($fileExt)); } } @@ -221,7 +223,7 @@ class CreateFolderController $fileExtList = []; $textFileExt = GeneralUtility::trimExplode(',', $GLOBALS['TYPO3_CONF_VARS']['SYS']['textfile_ext'], true); foreach ($textFileExt as $fileExt) { - if (GeneralUtility::verifyFilenameAgainstDenyPattern('.' . $fileExt)) { + if ($fileNameVerifier->isValid('.' . $fileExt)) { $fileExtList[] = strtoupper(htmlspecialchars($fileExt)); } } diff --git a/typo3/sysext/form/Classes/Mvc/Property/TypeConverter/UploadedFileReferenceConverter.php b/typo3/sysext/form/Classes/Mvc/Property/TypeConverter/UploadedFileReferenceConverter.php index 5c3693bb9353..34b9427c83c4 100644 --- a/typo3/sysext/form/Classes/Mvc/Property/TypeConverter/UploadedFileReferenceConverter.php +++ b/typo3/sysext/form/Classes/Mvc/Property/TypeConverter/UploadedFileReferenceConverter.php @@ -18,6 +18,7 @@ namespace TYPO3\CMS\Form\Mvc\Property\TypeConverter; use TYPO3\CMS\Core\Log\LogManager; use TYPO3\CMS\Core\Resource\File as File; use TYPO3\CMS\Core\Resource\FileReference as CoreFileReference; +use TYPO3\CMS\Core\Resource\Security\FileNameValidator; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Domain\Model\AbstractFileFolder; use TYPO3\CMS\Extbase\Domain\Model\FileReference as ExtbaseFileReference; @@ -193,7 +194,7 @@ class UploadedFileReferenceConverter extends AbstractTypeConverter array $uploadInfo, PropertyMappingConfigurationInterface $configuration ): ExtbaseFileReference { - if (!GeneralUtility::verifyFilenameAgainstDenyPattern($uploadInfo['name'])) { + if (!GeneralUtility::makeInstance(FileNameValidator::class)->isValid($uploadInfo['name'])) { throw new TypeConverterException('Uploading files with PHP file extensions is not allowed!', 1471710357); } diff --git a/typo3/sysext/impexp/Classes/Import.php b/typo3/sysext/impexp/Classes/Import.php index 29ad36171db1..9ab4e98a04b5 100644 --- a/typo3/sysext/impexp/Classes/Import.php +++ b/typo3/sysext/impexp/Classes/Import.php @@ -25,6 +25,7 @@ use TYPO3\CMS\Core\Resource\File; use TYPO3\CMS\Core\Resource\FileInterface; use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Resource\ResourceStorage; +use TYPO3\CMS\Core\Resource\Security\FileNameValidator; use TYPO3\CMS\Core\Resource\StorageRepository; use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -1544,7 +1545,7 @@ class Import extends ImportExport } } $fI = GeneralUtility::split_fileref($fileName); - if (!GeneralUtility::verifyFilenameAgainstDenyPattern($fI['file'])) { + if (!GeneralUtility::makeInstance(FileNameValidator::class)->isValid($fI['file'])) { $this->error('ERROR: Filename "' . $fileName . '" failed against extension check or deny-pattern!'); return false; } diff --git a/typo3/sysext/impexp/Classes/ImportExport.php b/typo3/sysext/impexp/Classes/ImportExport.php index 0f1c6bfcd688..f80e6e703fb0 100644 --- a/typo3/sysext/impexp/Classes/ImportExport.php +++ b/typo3/sysext/impexp/Classes/ImportExport.php @@ -22,6 +22,7 @@ use TYPO3\CMS\Core\Imaging\IconFactory; use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\Resource\Exception\InsufficientFolderAccessPermissionsException; use TYPO3\CMS\Core\Resource\ResourceFactory; +use TYPO3\CMS\Core\Resource\Security\FileNameValidator; use TYPO3\CMS\Core\Type\Bitmask\Permission; use TYPO3\CMS\Core\Utility\DebugUtility; use TYPO3\CMS\Core\Utility\DiffUtility; @@ -764,7 +765,7 @@ abstract class ImportExport $fileProcObj = $this->getFileProcObj(); if ($fileProcObj->actionPerms['addFile']) { $testFI = GeneralUtility::split_fileref(Environment::getPublicPath() . '/' . $fI['relFileName']); - if (!GeneralUtility::verifyFilenameAgainstDenyPattern($testFI['file'])) { + if (!GeneralUtility::makeInstance(FileNameValidator::class)->isValid($testFI['file'])) { $pInfo['msg'] .= 'File extension was not allowed!'; } } else { diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ConstantMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ConstantMatcher.php index cc0f0060167b..b745c1cb20b6 100644 --- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ConstantMatcher.php +++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ConstantMatcher.php @@ -220,4 +220,14 @@ return [ 'Deprecation-89866-Global-TYPO3-information-related-constants.rst' ] ], + 'FILE_DENY_PATTERN_DEFAULT' => [ + 'restFiles' => [ + 'Deprecation-90147-UnifiedFileNameValidator.rst' + ] + ], + 'PHP_EXTENSIONS_DEFAULT' => [ + 'restFiles' => [ + 'Deprecation-90147-UnifiedFileNameValidator.rst' + ] + ], ]; diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php index d5991e6a721c..e0384608f01e 100644 --- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php +++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php @@ -973,4 +973,11 @@ return [ 'Deprecation-90800-GeneralUtilityisRunningOnCgiServerApi.rst', ], ], + 'TYPO3\CMS\Core\Utility\GeneralUtility::verifyFilenameAgainstDenyPattern' => [ + 'numberOfMandatoryArguments' => 1, + 'maximumNumberOfArguments' => 1, + 'restFiles' => [ + 'Deprecation-90147-UnifiedFileNameValidator.rst' + ], + ], ]; diff --git a/typo3/sysext/recordlist/Classes/View/FolderUtilityRenderer.php b/typo3/sysext/recordlist/Classes/View/FolderUtilityRenderer.php index 354d02ba380b..2ec45c6185f7 100644 --- a/typo3/sysext/recordlist/Classes/View/FolderUtilityRenderer.php +++ b/typo3/sysext/recordlist/Classes/View/FolderUtilityRenderer.php @@ -19,6 +19,7 @@ use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Localization\LanguageService; use TYPO3\CMS\Core\Resource\Folder; use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry; +use TYPO3\CMS\Core\Resource\Security\FileNameValidator; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\HttpUtility; use TYPO3\CMS\Recordlist\Tree\View\LinkParameterProviderInterface; @@ -124,8 +125,9 @@ class FolderUtilityRenderer $lang = $this->getLanguageService(); // Create a list of allowed file extensions with the readable format "youtube, vimeo" etc. $fileExtList = []; + $fileNameVerifier = GeneralUtility::makeInstance(FileNameValidator::class); foreach ($allowedExtensions as $fileExt) { - if (GeneralUtility::verifyFilenameAgainstDenyPattern('.' . $fileExt)) { + if ($fileNameVerifier->isValid('.' . $fileExt)) { $fileExtList[] = '<span class="label label-success">' . strtoupper(htmlspecialchars($fileExt)) . '</span>'; } @@ -185,7 +187,7 @@ class FolderUtilityRenderer $fileExtList = []; $onlineMediaFileExt = OnlineMediaHelperRegistry::getInstance()->getSupportedFileExtensions(); foreach ($onlineMediaFileExt as $fileExt) { - if (GeneralUtility::verifyFilenameAgainstDenyPattern('.' . $fileExt) + if ($fileNameVerifier->isValid('.' . $fileExt) && (empty($allowedExtensions) || in_array($fileExt, $allowedExtensions, true)) ) { $fileExtList[] = '<span class="label label-success">' diff --git a/typo3/sysext/reports/Classes/Report/Status/SecurityStatus.php b/typo3/sysext/reports/Classes/Report/Status/SecurityStatus.php index d435a1d12ebb..b532d19d7f7a 100644 --- a/typo3/sysext/reports/Classes/Report/Status/SecurityStatus.php +++ b/typo3/sysext/reports/Classes/Report/Status/SecurityStatus.php @@ -23,6 +23,7 @@ use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction; use TYPO3\CMS\Core\Localization\LanguageService; +use TYPO3\CMS\Core\Resource\Security\FileNameValidator; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Reports\RequestAwareStatusProviderInterface; use TYPO3\CMS\Reports\Status as ReportStatus; @@ -202,16 +203,14 @@ class SecurityStatus implements RequestAwareStatusProviderInterface $value = $this->getLanguageService()->getLL('status_ok'); $message = ''; $severity = ReportStatus::OK; - $defaultParts = GeneralUtility::trimExplode('|', FILE_DENY_PATTERN_DEFAULT, true); - $givenParts = GeneralUtility::trimExplode('|', $GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'], true); - $result = array_intersect($defaultParts, $givenParts); - if ($defaultParts !== $result) { + $fileAccessCheck = GeneralUtility::makeInstance(FileNameValidator::class); + if ($fileAccessCheck->missingImportantPatterns()) { $value = $this->getLanguageService()->getLL('status_insecure'); $severity = ReportStatus::ERROR; $message = sprintf( $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_deny_pattern_partsNotPresent'), - '<br /><pre>' . htmlspecialchars(FILE_DENY_PATTERN_DEFAULT) . '</pre><br />' + '<br /><pre>' . htmlspecialchars($fileAccessCheck::DEFAULT_FILE_DENY_PATTERN) . '</pre><br />' ); } @@ -230,8 +229,9 @@ class SecurityStatus implements RequestAwareStatusProviderInterface $message = ''; $severity = ReportStatus::OK; - if ($GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] != FILE_DENY_PATTERN_DEFAULT - && GeneralUtility::verifyFilenameAgainstDenyPattern('.htaccess')) { + $fileNameAccess = GeneralUtility::makeInstance(FileNameValidator::class); + if ($fileNameAccess->customFileDenyPatternConfigured() + && $fileNameAccess->isValid('.htaccess')) { $value = $this->getLanguageService()->getLL('status_insecure'); $severity = ReportStatus::ERROR; $message = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.file_deny_htaccess'); -- GitLab