Loading core/modules/system/src/EventSubscriber/SecurityFileUploadEventSubscriber.php +6 −11 Original line number Diff line number Diff line Loading @@ -52,22 +52,17 @@ public function sanitizeName(FileUploadSanitizeNameEvent $event): void { // http://php.net/manual/security.filesystem.nullbytes.php $filename = str_replace(chr(0), '', $filename); if ($filename !== $event->getFilename()) { $event->setFilename($filename)->setSecurityRename(); } // Split up the filename by periods. The first part becomes the basename, // the last part the final extension. $filename_parts = explode('.', $filename); // Remove file basename. $filename = array_shift($filename_parts); // Remove final extension. // Remove final extension. In the case of dot filenames this will be empty. $final_extension = (string) array_pop($filename_parts); // Check if we're dealing with a dot file that is also an insecure extension // e.g. .htaccess. In this scenario there is only one 'part' and the // extension becomes the filename. We use the original filename from the // event rather than the trimmed version above. $insecure_uploads = $this->configFactory->get('system.file')->get('allow_insecure_uploads'); if (!$insecure_uploads && $final_extension === '' && str_contains($event->getFilename(), '.') && in_array(strtolower($filename), FileSystemInterface::INSECURE_EXTENSIONS, TRUE)) { $final_extension = $filename; $filename = ''; } $extensions = $event->getAllowedExtensions(); if (!empty($extensions) && !in_array(strtolower($final_extension), $extensions, TRUE)) { Loading @@ -81,7 +76,7 @@ public function sanitizeName(FileUploadSanitizeNameEvent $event): void { return; } if (!$insecure_uploads && in_array(strtolower($final_extension), FileSystemInterface::INSECURE_EXTENSIONS, TRUE)) { if (!$this->configFactory->get('system.file')->get('allow_insecure_uploads') && in_array(strtolower($final_extension), FileSystemInterface::INSECURE_EXTENSIONS, TRUE)) { if (empty($extensions) || in_array('txt', $extensions, TRUE)) { // Add .txt to potentially executable files prior to munging to help // prevent exploits. This results in a filenames like filename.php being Loading core/modules/system/tests/src/Unit/Event/SecurityFileUploadEventSubscriberTest.php +7 −10 Original line number Diff line number Diff line Loading @@ -85,12 +85,17 @@ public static function provideFilenames() { 'no extension produces no errors' => ['foo', '', 'foo'], 'filename is munged' => ['foo.phar.png.php.jpg', 'jpg png', 'foo.phar_.png_.php_.jpg'], 'filename is munged regardless of case' => ['FOO.pHAR.PNG.PhP.jpg', 'jpg png', 'FOO.pHAR_.PNG_.PhP_.jpg'], 'null bytes are removed' => ['foo' . chr(0) . '.txt' . chr(0), '', 'foo.txt'], 'null bytes are removed even if some extensions are allowed' => [ 'foo' . chr(0) . '.html' . chr(0), 'txt', 'foo.html', ], 'dot files are renamed' => ['.git', '', 'git'], 'htaccess files are renamed even if allowed' => ['.htaccess', 'htaccess txt', '.htaccess_.txt', '.htaccess'], 'htaccess files are renamed even if allowed' => ['.htaccess', 'htaccess txt', 'htaccess'], '.phtml extension allowed with .phtml file' => ['foo.phtml', 'phtml', 'foo.phtml'], '.phtml, .txt extension allowed with .phtml file' => ['foo.phtml', 'phtml txt', 'foo.phtml_.txt', 'foo.phtml'], 'All extensions allowed with .phtml file' => ['foo.phtml', '', 'foo.phtml_.txt', 'foo.phtml'], 'dot files are renamed even if allowed and not in security list' => ['.git', 'git', 'git'], ]; } Loading Loading @@ -147,18 +152,10 @@ public static function provideFilenamesNoMunge() { // The following filename would be rejected by 'FileExtension' constraint // and therefore remains unchanged. '.php is not munged when it would be rejected' => ['foo.php.php', 'jpg'], '.php is not munged when it would be rejected and filename contains null byte character' => [ 'foo.' . chr(0) . 'php.php', 'jpg', ], 'extension less files are not munged when they would be rejected' => [ 'foo', 'jpg', ], 'dot files are not munged when they would be rejected' => [ '.htaccess', 'jpg png', ], ]; } Loading Loading
core/modules/system/src/EventSubscriber/SecurityFileUploadEventSubscriber.php +6 −11 Original line number Diff line number Diff line Loading @@ -52,22 +52,17 @@ public function sanitizeName(FileUploadSanitizeNameEvent $event): void { // http://php.net/manual/security.filesystem.nullbytes.php $filename = str_replace(chr(0), '', $filename); if ($filename !== $event->getFilename()) { $event->setFilename($filename)->setSecurityRename(); } // Split up the filename by periods. The first part becomes the basename, // the last part the final extension. $filename_parts = explode('.', $filename); // Remove file basename. $filename = array_shift($filename_parts); // Remove final extension. // Remove final extension. In the case of dot filenames this will be empty. $final_extension = (string) array_pop($filename_parts); // Check if we're dealing with a dot file that is also an insecure extension // e.g. .htaccess. In this scenario there is only one 'part' and the // extension becomes the filename. We use the original filename from the // event rather than the trimmed version above. $insecure_uploads = $this->configFactory->get('system.file')->get('allow_insecure_uploads'); if (!$insecure_uploads && $final_extension === '' && str_contains($event->getFilename(), '.') && in_array(strtolower($filename), FileSystemInterface::INSECURE_EXTENSIONS, TRUE)) { $final_extension = $filename; $filename = ''; } $extensions = $event->getAllowedExtensions(); if (!empty($extensions) && !in_array(strtolower($final_extension), $extensions, TRUE)) { Loading @@ -81,7 +76,7 @@ public function sanitizeName(FileUploadSanitizeNameEvent $event): void { return; } if (!$insecure_uploads && in_array(strtolower($final_extension), FileSystemInterface::INSECURE_EXTENSIONS, TRUE)) { if (!$this->configFactory->get('system.file')->get('allow_insecure_uploads') && in_array(strtolower($final_extension), FileSystemInterface::INSECURE_EXTENSIONS, TRUE)) { if (empty($extensions) || in_array('txt', $extensions, TRUE)) { // Add .txt to potentially executable files prior to munging to help // prevent exploits. This results in a filenames like filename.php being Loading
core/modules/system/tests/src/Unit/Event/SecurityFileUploadEventSubscriberTest.php +7 −10 Original line number Diff line number Diff line Loading @@ -85,12 +85,17 @@ public static function provideFilenames() { 'no extension produces no errors' => ['foo', '', 'foo'], 'filename is munged' => ['foo.phar.png.php.jpg', 'jpg png', 'foo.phar_.png_.php_.jpg'], 'filename is munged regardless of case' => ['FOO.pHAR.PNG.PhP.jpg', 'jpg png', 'FOO.pHAR_.PNG_.PhP_.jpg'], 'null bytes are removed' => ['foo' . chr(0) . '.txt' . chr(0), '', 'foo.txt'], 'null bytes are removed even if some extensions are allowed' => [ 'foo' . chr(0) . '.html' . chr(0), 'txt', 'foo.html', ], 'dot files are renamed' => ['.git', '', 'git'], 'htaccess files are renamed even if allowed' => ['.htaccess', 'htaccess txt', '.htaccess_.txt', '.htaccess'], 'htaccess files are renamed even if allowed' => ['.htaccess', 'htaccess txt', 'htaccess'], '.phtml extension allowed with .phtml file' => ['foo.phtml', 'phtml', 'foo.phtml'], '.phtml, .txt extension allowed with .phtml file' => ['foo.phtml', 'phtml txt', 'foo.phtml_.txt', 'foo.phtml'], 'All extensions allowed with .phtml file' => ['foo.phtml', '', 'foo.phtml_.txt', 'foo.phtml'], 'dot files are renamed even if allowed and not in security list' => ['.git', 'git', 'git'], ]; } Loading Loading @@ -147,18 +152,10 @@ public static function provideFilenamesNoMunge() { // The following filename would be rejected by 'FileExtension' constraint // and therefore remains unchanged. '.php is not munged when it would be rejected' => ['foo.php.php', 'jpg'], '.php is not munged when it would be rejected and filename contains null byte character' => [ 'foo.' . chr(0) . 'php.php', 'jpg', ], 'extension less files are not munged when they would be rejected' => [ 'foo', 'jpg', ], 'dot files are not munged when they would be rejected' => [ '.htaccess', 'jpg png', ], ]; } Loading