Skip to content

Commit 96878f5

Browse files
authoredJul 20, 2022
Security backdrop#179: Tighten image style derivative access checks.
1 parent 4ccb234 commit 96878f5

File tree

7 files changed

+129
-17
lines changed

7 files changed

+129
-17
lines changed
 

‎core/includes/stream_wrappers.inc

+17
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,23 @@ class BackdropPublicStreamWrapper extends BackdropLocalStreamWrapper {
932932
$path = str_replace('\\', '/', $this->getTarget());
933933
return $GLOBALS['base_url'] . '/' . self::getDirectoryPath() . '/' . backdrop_encode_path($path);
934934
}
935+
936+
/**
937+
* {@inheritdoc}
938+
*/
939+
protected function getLocalPath($uri = NULL) {
940+
$path = parent::getLocalPath($uri);
941+
942+
$private_path = config_get('system.core', 'file_private_path');
943+
if ($private_path) {
944+
$private_path = realpath($private_path);
945+
if ($private_path && strpos($path, $private_path) === 0) {
946+
return FALSE;
947+
}
948+
}
949+
950+
return $path;
951+
}
935952
}
936953

937954
/**

‎core/modules/image/image.module

+57-16
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,7 @@ function image_style_deliver($style, $scheme) {
702702

703703
$image_uri = $scheme . '://' . $target;
704704
$derivative_uri = image_style_path($style['name'], $image_uri);
705+
$derivative_scheme = file_uri_scheme($derivative_uri);
705706

706707
// Image styles are not allowed to modify previously generated style images.
707708
if (strpos($image_uri, $scheme . '://styles/') === 0) {
@@ -711,21 +712,25 @@ function image_style_deliver($style, $scheme) {
711712
backdrop_exit();
712713
}
713714

714-
// If using the private scheme, let other modules provide headers and
715-
// control access to the file.
716-
if ($scheme == 'private') {
717-
if (file_exists($derivative_uri)) {
718-
file_download($scheme, file_uri_target($derivative_uri));
715+
$core_schemes = array('public', 'private', 'temporary');
716+
$additional_public_schemes = array_diff((array) config_get('system.core', 'file_additional_public_schemes'), $core_schemes);
717+
$public_schemes = array_merge(array('public'), $additional_public_schemes);
718+
$is_public = in_array($derivative_scheme, $public_schemes, TRUE);
719+
720+
if ($scheme == 'private' && file_exists($derivative_uri)) {
721+
file_download($scheme, file_uri_target($derivative_uri));
722+
}
723+
724+
// Other non-public schemes may add additional headers and control access to
725+
// the file.
726+
if (!$is_public) {
727+
$headers = file_download_headers($image_uri);
728+
if (empty($headers)) {
729+
return MENU_ACCESS_DENIED;
719730
}
720-
else {
721-
$headers = file_download_headers($image_uri);
722-
if (empty($headers)) {
723-
return MENU_ACCESS_DENIED;
724-
}
725-
if (count($headers)) {
726-
foreach ($headers as $name => $value) {
727-
backdrop_add_http_header($name, $value);
728-
}
731+
if (count($headers)) {
732+
foreach ($headers as $name => $value) {
733+
backdrop_add_http_header($name, $value);
729734
}
730735
}
731736
}
@@ -745,9 +750,12 @@ function image_style_deliver($style, $scheme) {
745750
}
746751

747752
// Confirm that the original source image exists before trying to process it.
748-
if (!is_file($image_uri)) {
753+
if (!_image_source_image_exists($image_uri)) {
749754
watchdog('image', 'Source image at %source_image_path not found while trying to generate derivative image at %derivative_path.', array('%source_image_path' => $image_uri, '%derivative_path' => $derivative_uri));
750-
return MENU_NOT_FOUND;
755+
backdrop_add_http_header('Status', '404 Not Found');
756+
backdrop_add_http_header('Content-Type', 'text/html; charset=utf-8');
757+
print t('Source image not found.');
758+
backdrop_exit();
751759
}
752760

753761
// Don't start generating the image if the derivative already exists or if
@@ -793,6 +801,39 @@ function image_style_deliver($style, $scheme) {
793801
}
794802
}
795803

804+
/**
805+
* Checks whether the provided source image exists.
806+
*
807+
* @param string $image_uri
808+
* The URI for the source image.
809+
*
810+
* @return bool
811+
* Whether the source image exists.
812+
*/
813+
function _image_source_image_exists($image_uri) {
814+
$exists = file_exists($image_uri);
815+
816+
// If the file doesn't exist, we can stop here.
817+
if (!$exists) {
818+
return FALSE;
819+
}
820+
821+
if (file_uri_scheme($image_uri) !== 'public') {
822+
return TRUE;
823+
}
824+
825+
$image_path = backdrop_realpath($image_uri);
826+
$private_path = config_get('system.core', 'file_private_path');
827+
if ($private_path) {
828+
$private_path = realpath($private_path);
829+
if ($private_path && strpos($image_path, $private_path) === 0) {
830+
return FALSE;
831+
}
832+
}
833+
834+
return TRUE;
835+
}
836+
796837
/**
797838
* Creates a new image derivative based on an image style.
798839
*

‎core/modules/system/config/system.core.json

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"default_nodes_main": 10,
2525
"error_level": "hide",
2626
"field_purge_batch_size": 200,
27+
"file_additional_public_schemes": [],
2728
"file_default_allowed_extensions": "jpg jpeg gif png txt doc docx xls xlsx pdf ppt pptx pps ppsx odt ods odp mp3 mov mp4 m4a m4v mpeg avi ogg oga ogv weba webp webm",
2829
"file_default_scheme": "public",
2930
"file_private_path": "",

‎core/modules/system/system.install

+12
Original file line numberDiff line numberDiff line change
@@ -2477,6 +2477,7 @@ function system_update_1025() {
24772477
*/
24782478
function system_update_1026() {
24792479
$config = config('system.core');
2480+
$config->set('file_additional_public_schemes', update_variable_get('file_additional_public_schemes', array()));
24802481
$config->set('file_default_scheme', update_variable_get('file_default_scheme', 'public'));
24812482
$config->set('file_public_path', update_variable_get('file_public_path', 'files'));
24822483
$config->set('file_temporary_path', update_variable_get('file_temporary_path', ''));
@@ -3304,6 +3305,17 @@ function system_update_1082() {
33043305
}
33053306
}
33063307

3308+
/**
3309+
* Set the default value for "file_additional_public_schemes" setting.
3310+
*/
3311+
function system_update_1083() {
3312+
$config = config('system.core');
3313+
if ($config->get('file_additional_public_schemes') == NULL) {
3314+
$config->set('file_additional_public_schemes', array());
3315+
$config->save();
3316+
}
3317+
}
3318+
33073319
/**
33083320
* @} End of "defgroup updates-7.x-to-1.x"
33093321
* The next series of updates should start at 2000.

‎core/modules/system/system.module

+18
Original file line numberDiff line numberDiff line change
@@ -4388,6 +4388,24 @@ function system_admin_paths() {
43884388
return $paths;
43894389
}
43904390

4391+
/**
4392+
* Implements hook_file_download().
4393+
*/
4394+
function system_file_download($uri) {
4395+
$core_schemes = array('public', 'private', 'temporary');
4396+
$additional_public_schemes = array_diff((array) config_get('system.core', 'file_additional_public_schemes'), $core_schemes);
4397+
if ($additional_public_schemes) {
4398+
$scheme = file_uri_scheme($uri);
4399+
if (in_array($scheme, $additional_public_schemes, TRUE)) {
4400+
return array(
4401+
// Returning any header grants access, and setting the 'Cache-Control'
4402+
// header is appropriate for public files.
4403+
'Cache-Control' => 'public',
4404+
);
4405+
}
4406+
}
4407+
}
4408+
43914409
/**
43924410
* Sets up a form to save information automatically.
43934411
*

‎core/modules/views/includes/utility.inc

+1-1
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ function views_discover_plugins() {
511511
* Get enabled display extenders.
512512
*/
513513
function views_get_enabled_display_extenders() {
514-
$enabled = array_filter(config_get('views.settings', 'display_extenders'));
514+
$enabled = array_filter((array) config_get('views.settings', 'display_extenders'));
515515
$options = views_fetch_plugin_names('display_extender');
516516
foreach ($options as $name => $plugin) {
517517
$enabled[$name] = $name;

‎settings.php

+23
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,29 @@
455455
*/
456456
//$config['system.core']['block_interest_cohort'] = FALSE;
457457

458+
/**
459+
* Additional public file schemes:
460+
*
461+
* Public schemes are URI schemes that allow download access to all users for
462+
* all files within that scheme.
463+
*
464+
* The "public" scheme is always public, and the "private" scheme is always
465+
* private, but other schemes, such as "https", "s3", "example", or others,
466+
* can be either public or private depending on the site. By default, they're
467+
* private, and access to individual files is controlled via
468+
* hook_file_download().
469+
*
470+
* Typically, if a scheme should be public, a module makes it public by
471+
* implementing hook_file_download(), and granting access to all users for all
472+
* files. This could be either the same module that provides the stream wrapper
473+
* for the scheme, or a different module that decides to make the scheme
474+
* public. However, in cases where a site needs to make a scheme public, but
475+
* is unable to add code in a module to do so, the scheme may be added to this
476+
* variable, the result of which is that system_file_download() grants public
477+
* access to all files within that scheme.
478+
*/
479+
//$config['system.core']['file_additional_public_schemes'] = array('example');
480+
458481
/**
459482
* Include a local settings file, if available.
460483
*

0 commit comments

Comments
 (0)
Please sign in to comment.