Loading core/assets/scaffold/files/default.settings.php +19 −0 Original line number Diff line number Diff line Loading @@ -513,6 +513,25 @@ */ # $settings['file_additional_public_schemes'] = ['example']; /** * File schemes whose paths should not be normalized: * * Normally, Drupal normalizes '/./' and '/../' segments in file URIs in order * to prevent unintended file access. For example, 'private://css/../image.png' * is normalized to 'private://image.png' before checking access to the file. * * On Windows, Drupal also replaces '\' with '/' in URIs for the local * filesystem. * * If file URIs with one or more scheme should not be normalized like this, then * list the schemes here. For example, if 'porcelain://china/./plate.png' should * not be normalized to 'porcelain://china/plate.png', then add 'porcelain' to * this array. In this case, make sure that the module providing the 'porcelain' * scheme does not allow unintended file access when using '/../' to move up the * directory tree. */ # $settings['file_sa_core_2023_005_schemes'] = ['porcelain']; /** * Private file path: * Loading core/lib/Drupal/Core/StreamWrapper/StreamWrapperManager.php +31 −0 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ namespace Drupal\Core\StreamWrapper; use Drupal\Core\Site\Settings; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerAwareTrait; Loading Loading @@ -242,6 +243,36 @@ public function normalizeUri($uri) { $target = $this->getTarget($uri); if ($target !== FALSE) { if (!in_array($scheme, Settings::get('file_sa_core_2023_005_schemes', []))) { $class = $this->getClass($scheme); $is_local = is_subclass_of($class, LocalStream::class); if ($is_local) { $target = str_replace(DIRECTORY_SEPARATOR, '/', $target); } $parts = explode('/', $target); $normalized_parts = []; while ($parts) { $part = array_shift($parts); if ($part === '' || $part === '.') { continue; } elseif ($part === '..' && $is_local && $normalized_parts === []) { $normalized_parts[] = $part; break; } elseif ($part === '..') { array_pop($normalized_parts); } else { $normalized_parts[] = $part; } } $target = implode('/', array_merge($normalized_parts, $parts)); } $uri = $scheme . '://' . $target; } } Loading core/modules/image/src/Controller/ImageStyleDownloadController.php +15 −1 Original line number Diff line number Diff line Loading @@ -114,6 +114,19 @@ public static function create(ContainerInterface $container) { public function deliver(Request $request, $scheme, ImageStyleInterface $image_style) { $target = $request->query->get('file'); $image_uri = $scheme . '://' . $target; $image_uri = $this->streamWrapperManager->normalizeUri($image_uri); if ($this->streamWrapperManager->isValidScheme($scheme)) { $normalized_target = $this->streamWrapperManager->getTarget($image_uri); if ($normalized_target !== FALSE) { if (!in_array($scheme, Settings::get('file_sa_core_2023_005_schemes', []))) { $parts = explode('/', $normalized_target); if (array_intersect($parts, ['.', '..'])) { throw new NotFoundHttpException(); } } } } // Check that the style is defined and the scheme is valid. $valid = !empty($image_style) && $this->streamWrapperManager->isValidScheme($scheme); Loading @@ -129,7 +142,8 @@ public function deliver(Request $request, $scheme, ImageStyleInterface $image_st // styles/<style_name>/... as structure, so we check if the $target variable // starts with styles/. $token = $request->query->get(IMAGE_DERIVATIVE_TOKEN, ''); $token_is_valid = hash_equals($image_style->getPathToken($image_uri), $token); $token_is_valid = hash_equals($image_style->getPathToken($image_uri), $token) || hash_equals($image_style->getPathToken($scheme . '://' . $target), $token); if (!$this->config('image.settings')->get('allow_insecure_derivatives') || strpos(ltrim($target, '\/'), 'styles/') === 0) { $valid = $valid && $token_is_valid; } Loading core/modules/image/src/Entity/ImageStyle.php +2 −2 Original line number Diff line number Diff line Loading @@ -205,11 +205,11 @@ public function buildUri($uri) { * {@inheritdoc} */ public function buildUrl($path, $clean_urls = NULL) { $uri = $this->buildUri($path); /** @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager */ $stream_wrapper_manager = \Drupal::service('stream_wrapper_manager'); $uri = $stream_wrapper_manager->normalizeUri($this->buildUri($path)); // The token query is added even if the // 'image.settings:allow_insecure_derivatives' configuration is TRUE, so // that the emitted links remain valid if it is changed back to the default Loading core/modules/system/src/FileDownloadController.php +1 −1 Original line number Diff line number Diff line Loading @@ -69,7 +69,7 @@ public static function create(ContainerInterface $container) { public function download(Request $request, $scheme = 'private') { $target = $request->query->get('file'); // Merge remaining path arguments into relative file path. $uri = $scheme . '://' . $target; $uri = $this->streamWrapperManager->normalizeUri($scheme . '://' . $target); if ($this->streamWrapperManager->isValidScheme($scheme) && is_file($uri)) { // Let other modules provide headers and controls access to the file. Loading Loading
core/assets/scaffold/files/default.settings.php +19 −0 Original line number Diff line number Diff line Loading @@ -513,6 +513,25 @@ */ # $settings['file_additional_public_schemes'] = ['example']; /** * File schemes whose paths should not be normalized: * * Normally, Drupal normalizes '/./' and '/../' segments in file URIs in order * to prevent unintended file access. For example, 'private://css/../image.png' * is normalized to 'private://image.png' before checking access to the file. * * On Windows, Drupal also replaces '\' with '/' in URIs for the local * filesystem. * * If file URIs with one or more scheme should not be normalized like this, then * list the schemes here. For example, if 'porcelain://china/./plate.png' should * not be normalized to 'porcelain://china/plate.png', then add 'porcelain' to * this array. In this case, make sure that the module providing the 'porcelain' * scheme does not allow unintended file access when using '/../' to move up the * directory tree. */ # $settings['file_sa_core_2023_005_schemes'] = ['porcelain']; /** * Private file path: * Loading
core/lib/Drupal/Core/StreamWrapper/StreamWrapperManager.php +31 −0 Original line number Diff line number Diff line Loading @@ -2,6 +2,7 @@ namespace Drupal\Core\StreamWrapper; use Drupal\Core\Site\Settings; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerAwareTrait; Loading Loading @@ -242,6 +243,36 @@ public function normalizeUri($uri) { $target = $this->getTarget($uri); if ($target !== FALSE) { if (!in_array($scheme, Settings::get('file_sa_core_2023_005_schemes', []))) { $class = $this->getClass($scheme); $is_local = is_subclass_of($class, LocalStream::class); if ($is_local) { $target = str_replace(DIRECTORY_SEPARATOR, '/', $target); } $parts = explode('/', $target); $normalized_parts = []; while ($parts) { $part = array_shift($parts); if ($part === '' || $part === '.') { continue; } elseif ($part === '..' && $is_local && $normalized_parts === []) { $normalized_parts[] = $part; break; } elseif ($part === '..') { array_pop($normalized_parts); } else { $normalized_parts[] = $part; } } $target = implode('/', array_merge($normalized_parts, $parts)); } $uri = $scheme . '://' . $target; } } Loading
core/modules/image/src/Controller/ImageStyleDownloadController.php +15 −1 Original line number Diff line number Diff line Loading @@ -114,6 +114,19 @@ public static function create(ContainerInterface $container) { public function deliver(Request $request, $scheme, ImageStyleInterface $image_style) { $target = $request->query->get('file'); $image_uri = $scheme . '://' . $target; $image_uri = $this->streamWrapperManager->normalizeUri($image_uri); if ($this->streamWrapperManager->isValidScheme($scheme)) { $normalized_target = $this->streamWrapperManager->getTarget($image_uri); if ($normalized_target !== FALSE) { if (!in_array($scheme, Settings::get('file_sa_core_2023_005_schemes', []))) { $parts = explode('/', $normalized_target); if (array_intersect($parts, ['.', '..'])) { throw new NotFoundHttpException(); } } } } // Check that the style is defined and the scheme is valid. $valid = !empty($image_style) && $this->streamWrapperManager->isValidScheme($scheme); Loading @@ -129,7 +142,8 @@ public function deliver(Request $request, $scheme, ImageStyleInterface $image_st // styles/<style_name>/... as structure, so we check if the $target variable // starts with styles/. $token = $request->query->get(IMAGE_DERIVATIVE_TOKEN, ''); $token_is_valid = hash_equals($image_style->getPathToken($image_uri), $token); $token_is_valid = hash_equals($image_style->getPathToken($image_uri), $token) || hash_equals($image_style->getPathToken($scheme . '://' . $target), $token); if (!$this->config('image.settings')->get('allow_insecure_derivatives') || strpos(ltrim($target, '\/'), 'styles/') === 0) { $valid = $valid && $token_is_valid; } Loading
core/modules/image/src/Entity/ImageStyle.php +2 −2 Original line number Diff line number Diff line Loading @@ -205,11 +205,11 @@ public function buildUri($uri) { * {@inheritdoc} */ public function buildUrl($path, $clean_urls = NULL) { $uri = $this->buildUri($path); /** @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager */ $stream_wrapper_manager = \Drupal::service('stream_wrapper_manager'); $uri = $stream_wrapper_manager->normalizeUri($this->buildUri($path)); // The token query is added even if the // 'image.settings:allow_insecure_derivatives' configuration is TRUE, so // that the emitted links remain valid if it is changed back to the default Loading
core/modules/system/src/FileDownloadController.php +1 −1 Original line number Diff line number Diff line Loading @@ -69,7 +69,7 @@ public static function create(ContainerInterface $container) { public function download(Request $request, $scheme = 'private') { $target = $request->query->get('file'); // Merge remaining path arguments into relative file path. $uri = $scheme . '://' . $target; $uri = $this->streamWrapperManager->normalizeUri($scheme . '://' . $target); if ($this->streamWrapperManager->isValidScheme($scheme) && is_file($uri)) { // Let other modules provide headers and controls access to the file. Loading