‪TYPO3CMS  ‪main
FileNameValidatorTest.php
Go to the documentation of this file.
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 
19 
20 use PHPUnit\Framework\Attributes\DataProvider;
21 use PHPUnit\Framework\Attributes\Test;
22 use PHPUnit\Framework\TestCase;
25 
26 final class ‪FileNameValidatorTest extends TestCase
27 {
28  public static function ‪deniedFilesWithoutDenyPatternDataProvider(): array
29  {
30  return [
31  'Nul character in file' => ['image' . "\0" . '.gif'],
32  'Nul character in file with .php' => ['image.php' . "\0" . '.gif'],
33  'Nul character and UTF-8 in file' => ['Ссылка' . "\0" . '.gif'],
34  'Nul character and Latin-1 in file' => ['ÉÐØ' . "\0" . '.gif'],
35  ];
36  }
37 
41  #[DataProvider('deniedFilesWithoutDenyPatternDataProvider')]
42  #[Test]
43  public function ‪verifyNulCharacterFilesAgainstPatternWithoutFileDenyPattern(string $deniedFile): void
44  {
45  $subject = new ‪FileNameValidator('');
46  self::assertFalse($subject->isValid($deniedFile));
47 
48  ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] = '';
49  $subject = new ‪FileNameValidator();
50  self::assertFalse($subject->isValid($deniedFile));
51  }
52 
53  public static function ‪deniedFilesWithDefaultDenyPatternDataProvider(): array
54  {
55  $data = [
56  'Nul character in file' => ['image' . "\0", '.gif'],
57  'Nul character in file with .php' => ['image.php' . "\0", '.gif'],
58  'Nul character and UTF-8 in file' => ['Ссылка' . "\0", '.gif'],
59  'Nul character and Latin-1 in file' => ['ÉÐØ' . "\0", '.gif'],
60  'Lower umlaut .php file' => ['üWithFile', '.php'],
61  'Upper umlaut .php file' => ['fileWithÜ', '.php'],
62  'invalid UTF-8-sequence' => ["\xc0" . 'file', '.php'],
63  'Could be overlong NUL in some UTF-8 implementations, invalid in RFC3629' => ["\xc0\x80" . 'file', '.php'],
64  'Regular .php file' => ['file', '.php'],
65  'Regular .php3 file' => ['file', '.php3'],
66  'Regular .php5 file' => ['file', '.php5'],
67  'Regular .php7 file' => ['file', '.php7'],
68  'Regular .phpsh file' => ['file', '.phpsh'],
69  'Regular .phtml file' => ['file', '.phtml'],
70  'Regular .pht file' => ['file', '.pht'],
71  'Regular .phar file' => ['file', '.phar'],
72  'Regular .shtml file' => ['file', '.shtml'],
73  'Regular .cgi file' => ['file', '.cgi'],
74  'Regular .pl file' => ['file', '.pl'],
75  'Wrapped .php file ' => ['file', '.php.txt'],
76  'Wrapped .php3 file' => ['file', '.php3.txt'],
77  'Wrapped .php5 file' => ['file', '.php5.txt'],
78  'Wrapped .php7 file' => ['file', '.php7.txt'],
79  'Wrapped .phpsh file' => ['file', '.phpsh.txt'],
80  'Wrapped .phtml file' => ['file', '.phtml.txt'],
81  'Wrapped .pht file' => ['file', '.pht.txt'],
82  'Wrapped .phar file' => ['file', '.phar.txt'],
83  'Wrapped .shtml file' => ['file', '.shtml.txt'],
84  'Wrapped .cgi file' => ['file', '.cgi.txt'],
85  // allowed "Wrapped .pl file" in order to allow language specific files containing ".pl."
86  '.htaccess file' => ['', '.htaccess'],
87  ];
88 
89  // Mixing with regular utf-8
90  $utf8Characters = 'Ссылка';
91  foreach ($data as $key => $value) {
92  if ($value[0] === '') {
93  continue;
94  }
95  $data[$key . ' with UTF-8 characters prepended'] = [$utf8Characters . $value[0], $value[1]];
96  $data[$key . ' with UTF-8 characters appended'] = [$value[0] . $utf8Characters, $value[1]];
97  }
98 
99  // combine to single value
100  $data = array_map(
101  static function (array $values): array {
102  return [implode('', $values)];
103  },
104  $data
105  );
106 
107  // Encoding with UTF-16
108  foreach ($data as $key => $value) {
109  $data[$key . ' encoded with UTF-16'] = [mb_convert_encoding($value[0], 'UTF-16')];
110  }
111 
112  return $data;
113  }
114 
118  #[DataProvider('deniedFilesWithDefaultDenyPatternDataProvider')]
119  #[Test]
120  public function ‪isValidDetectsNotAllowedFiles(string $deniedFile): void
121  {
122  $subject = new ‪FileNameValidator();
123  self::assertFalse($subject->isValid($deniedFile));
124  }
125 
126  public static function ‪insecureFilesDataProvider(): array
127  {
128  return [
129  'Classic php file' => ['user.php'],
130  'A random .htaccess file' => ['.htaccess'],
131  'Wrapped .php file' => ['file.php.txt'],
132  ];
133  }
134 
135  #[DataProvider('insecureFilesDataProvider')]
136  #[Test]
137  public function ‪isValidAcceptsNotAllowedFilesDueToInsecureSetting(string $fileName): void
138  {
139  ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] = '\\.phc$';
140  $subject = new ‪FileNameValidator();
141  self::assertTrue($subject->isValid($fileName));
142  }
143 
144  public static function ‪allowedFilesDataProvider(): array
145  {
146  return [
147  'Regular .gif file' => ['image.gif'],
148  'Regular uppercase .gif file' => ['IMAGE.gif'],
149  'UTF-8 .gif file' => ['Ссылка.gif'],
150  'Lower umlaut .jpg file' => ['üWithFile.jpg'],
151  'Upper umlaut .png file' => ['fileWithÜ.png'],
152  'Latin-1 .gif file' => ['ÉÐØ.gif'],
153  'Wrapped .pl file' => ['file.pl.txt'],
154  ];
155  }
156 
160  #[DataProvider('allowedFilesDataProvider')]
161  #[Test]
162  public function ‪isValidAcceptAllowedFiles(string $allowedFile): void
163  {
164  $subject = new ‪FileNameValidator();
165  self::assertTrue($subject->isValid($allowedFile));
166  }
167 
168  #[Test]
169  public function ‪isCustomDenyPatternConfigured(): void
170  {
171  $subject = new ‪FileNameValidator('nothing-really');
172  self::assertTrue($subject->customFileDenyPatternConfigured());
173  ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] = 'something-else';
174  $subject = new ‪FileNameValidator();
175  self::assertTrue($subject->customFileDenyPatternConfigured());
176  ‪$GLOBALS['TYPO3_CONF_VARS']['BE']['fileDenyPattern'] = ‪FileNameValidator::DEFAULT_FILE_DENY_PATTERN;
177  $subject = new ‪FileNameValidator();
178  self::assertFalse($subject->customFileDenyPatternConfigured());
180  self::assertFalse($subject->customFileDenyPatternConfigured());
181  }
182 
183  #[Test]
185  {
186  $subject = new ‪FileNameValidator('\\.php$|.php8$');
187  self::assertTrue($subject->missingImportantPatterns());
189  self::assertFalse($subject->missingImportantPatterns());
190  }
191 
195  public static function ‪phpExtensionDataProvider(): array
196  {
197  $data = [];
198  $fileName = ‪StringUtility::getUniqueId('filename');
199  $phpExtensions = ['php', 'php3', 'php4', 'php5', 'php7', 'phpsh', 'phtml', 'pht'];
200  foreach ($phpExtensions as $extension) {
201  $data[] = [$fileName . '.' . $extension];
202  $data[] = [$fileName . '.' . $extension . '.txt'];
203  }
204  return $data;
205  }
206 
210  #[DataProvider('phpExtensionDataProvider')]
211  #[Test]
212  public function ‪defaultFileDenyPatternMatchesPhpExtension(string $fileName): void
213  {
214  self::assertGreaterThan(0, preg_match('/' . ‪FileNameValidator::DEFAULT_FILE_DENY_PATTERN . '/', $fileName), $fileName);
215  }
216 
220  #[DataProvider('phpExtensionDataProvider')]
221  #[Test]
222  public function ‪invalidPhpExtensionIsDetected(string $fileName): void
223  {
224  $subject = new ‪FileNameValidator();
225  self::assertFalse($subject->isValid($fileName));
226  }
227 }
‪TYPO3\CMS\Core\Resource\Security\FileNameValidator\DEFAULT_FILE_DENY_PATTERN
‪const DEFAULT_FILE_DENY_PATTERN
Definition: FileNameValidator.php:29
‪TYPO3\CMS\Core\Tests\Unit\Resource\Security\FileNameValidatorTest\isCustomDenyPatternConfigured
‪isCustomDenyPatternConfigured()
Definition: FileNameValidatorTest.php:169
‪TYPO3\CMS\Core\Tests\Unit\Resource\Security\FileNameValidatorTest\deniedFilesWithoutDenyPatternDataProvider
‪static deniedFilesWithoutDenyPatternDataProvider()
Definition: FileNameValidatorTest.php:28
‪TYPO3\CMS\Core\Resource\Security\FileNameValidator
Definition: FileNameValidator.php:25
‪TYPO3\CMS\Core\Tests\Unit\Resource\Security\FileNameValidatorTest\allowedFilesDataProvider
‪static allowedFilesDataProvider()
Definition: FileNameValidatorTest.php:144
‪TYPO3\CMS\Core\Tests\Unit\Resource\Security\FileNameValidatorTest\verifyNulCharacterFilesAgainstPatternWithoutFileDenyPattern
‪verifyNulCharacterFilesAgainstPatternWithoutFileDenyPattern(string $deniedFile)
Definition: FileNameValidatorTest.php:43
‪TYPO3\CMS\Core\Tests\Unit\Resource\Security
Definition: FileNameValidatorTest.php:18
‪TYPO3\CMS\Core\Tests\Unit\Resource\Security\FileNameValidatorTest\phpExtensionDataProvider
‪static phpExtensionDataProvider()
Definition: FileNameValidatorTest.php:195
‪TYPO3\CMS\Core\Tests\Unit\Resource\Security\FileNameValidatorTest\invalidPhpExtensionIsDetected
‪invalidPhpExtensionIsDetected(string $fileName)
Definition: FileNameValidatorTest.php:222
‪TYPO3\CMS\Core\Tests\Unit\Resource\Security\FileNameValidatorTest\customFileDenyPatternFindsMissingImportantParts
‪customFileDenyPatternFindsMissingImportantParts()
Definition: FileNameValidatorTest.php:184
‪TYPO3\CMS\Core\Tests\Unit\Resource\Security\FileNameValidatorTest
Definition: FileNameValidatorTest.php:27
‪TYPO3\CMS\Core\Tests\Unit\Resource\Security\FileNameValidatorTest\isValidAcceptAllowedFiles
‪isValidAcceptAllowedFiles(string $allowedFile)
Definition: FileNameValidatorTest.php:162
‪TYPO3\CMS\Core\Tests\Unit\Resource\Security\FileNameValidatorTest\deniedFilesWithDefaultDenyPatternDataProvider
‪static deniedFilesWithDefaultDenyPatternDataProvider()
Definition: FileNameValidatorTest.php:53
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Tests\Unit\Resource\Security\FileNameValidatorTest\defaultFileDenyPatternMatchesPhpExtension
‪defaultFileDenyPatternMatchesPhpExtension(string $fileName)
Definition: FileNameValidatorTest.php:212
‪TYPO3\CMS\Core\Tests\Unit\Resource\Security\FileNameValidatorTest\isValidAcceptsNotAllowedFilesDueToInsecureSetting
‪isValidAcceptsNotAllowedFilesDueToInsecureSetting(string $fileName)
Definition: FileNameValidatorTest.php:137
‪TYPO3\CMS\Core\Utility\StringUtility
Definition: StringUtility.php:24
‪TYPO3\CMS\Core\Tests\Unit\Resource\Security\FileNameValidatorTest\isValidDetectsNotAllowedFiles
‪isValidDetectsNotAllowedFiles(string $deniedFile)
Definition: FileNameValidatorTest.php:120
‪TYPO3\CMS\Core\Tests\Unit\Resource\Security\FileNameValidatorTest\insecureFilesDataProvider
‪static insecureFilesDataProvider()
Definition: FileNameValidatorTest.php:126
‪TYPO3\CMS\Core\Utility\StringUtility\getUniqueId
‪static getUniqueId(string $prefix='')
Definition: StringUtility.php:57