Skip to content

Commit 2552b99

Browse files
committed
Update to security release Drupal 7.57 (SA-CORE-2018-001).
2 parents 33a5b3d + a5f2394 commit 2552b99

File tree

9 files changed

+184
-6
lines changed

9 files changed

+184
-6
lines changed

CHANGELOG.txt

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11

2+
Drupal 7.57, 2018-02-21
3+
-----------------------
4+
- Fixed security issues (multiple vulnerabilities). See SA-CORE-2018-001.
5+
26
Drupal 7.56, 2017-06-21
37
-----------------------
48
- Fixed security issues (access bypass). See SA-CORE-2017-003.

includes/bootstrap.inc

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
/**
99
* The current system version.
1010
*/
11-
define('VERSION', '7.56');
11+
define('VERSION', '7.57');
1212

1313
/**
1414
* Core API compatibility.

includes/common.inc

+4-1
Original file line numberDiff line numberDiff line change
@@ -2236,8 +2236,11 @@ function url($path = NULL, array $options = array()) {
22362236
'prefix' => ''
22372237
);
22382238

2239+
// Determine whether this is an external link, but ensure that the current
2240+
// path is always treated as internal by default (to prevent external link
2241+
// injection vulnerabilities).
22392242
if (!isset($options['external'])) {
2240-
$options['external'] = url_is_external($path);
2243+
$options['external'] = $path === $_GET['q'] ? FALSE : url_is_external($path);
22412244
}
22422245

22432246
// Preserve the original path before altering or aliasing.

misc/drupal.js

+37-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,42 @@ $.fn.init = function (selector, context, rootjQuery) {
2727
};
2828
$.fn.init.prototype = jquery_init.prototype;
2929

30+
/**
31+
* Pre-filter Ajax requests to guard against XSS attacks.
32+
*
33+
* See https://github.com/jquery/jquery/issues/2432
34+
*/
35+
if ($.ajaxPrefilter) {
36+
// For newer versions of jQuery, use an Ajax prefilter to prevent
37+
// auto-executing script tags from untrusted domains. This is similar to the
38+
// fix that is built in to jQuery 3.0 and higher.
39+
$.ajaxPrefilter(function (s) {
40+
if (s.crossDomain) {
41+
s.contents.script = false;
42+
}
43+
});
44+
}
45+
else if ($.httpData) {
46+
// For the version of jQuery that ships with Drupal core, override
47+
// jQuery.httpData to prevent auto-detecting "script" data types from
48+
// untrusted domains.
49+
var jquery_httpData = $.httpData;
50+
$.httpData = function (xhr, type, s) {
51+
// @todo Consider backporting code from newer jQuery versions to check for
52+
// a cross-domain request here, rather than using Drupal.urlIsLocal() to
53+
// block scripts from all URLs that are not on the same site.
54+
if (!type && !Drupal.urlIsLocal(s.url)) {
55+
var content_type = xhr.getResponseHeader('content-type') || '';
56+
if (content_type.indexOf('javascript') >= 0) {
57+
// Default to a safe data type.
58+
type = 'text';
59+
}
60+
}
61+
return jquery_httpData.call(this, xhr, type, s);
62+
};
63+
$.httpData.prototype = jquery_httpData.prototype;
64+
}
65+
3066
/**
3167
* Attach all registered behaviors to a page element.
3268
*
@@ -137,7 +173,7 @@ Drupal.detachBehaviors = function (context, settings, trigger) {
137173
*/
138174
Drupal.checkPlain = function (str) {
139175
var character, regex,
140-
replace = { '&': '&amp;', '"': '&quot;', '<': '&lt;', '>': '&gt;' };
176+
replace = { '&': '&amp;', "'": '&#39;', '"': '&quot;', '<': '&lt;', '>': '&gt;' };
141177
str = String(str);
142178
for (character in replace) {
143179
if (replace.hasOwnProperty(character)) {

modules/file/file.module

+14-2
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ function file_file_download($uri, $field_type = 'file') {
140140
}
141141

142142
// Find out which (if any) fields of this type contain the file.
143-
$references = file_get_file_references($file, NULL, FIELD_LOAD_CURRENT, $field_type);
143+
$references = file_get_file_references($file, NULL, FIELD_LOAD_CURRENT, $field_type, FALSE);
144144

145145
// Stop processing if there are no references in order to avoid returning
146146
// headers for files controlled by other modules. Make an exception for
@@ -1067,11 +1067,18 @@ function file_icon_map($file) {
10671067
* @param $field_type
10681068
* (optional) The name of a field type. If given, limits the reference check
10691069
* to fields of the given type.
1070+
* @param $check_access
1071+
* (optional) A boolean that specifies whether the permissions of the current
1072+
* user should be checked when retrieving references. If FALSE, all
1073+
* references to the file are returned. If TRUE, only references from
1074+
* entities that the current user has access to are returned. Defaults to
1075+
* TRUE for backwards compatibility reasons, but FALSE is recommended for
1076+
* most situations.
10701077
*
10711078
* @return
10721079
* An integer value.
10731080
*/
1074-
function file_get_file_references($file, $field = NULL, $age = FIELD_LOAD_REVISION, $field_type = 'file') {
1081+
function file_get_file_references($file, $field = NULL, $age = FIELD_LOAD_REVISION, $field_type = 'file', $check_access = TRUE) {
10751082
$references = drupal_static(__FUNCTION__, array());
10761083
$fields = isset($field) ? array($field['field_name'] => $field) : field_info_fields();
10771084

@@ -1082,6 +1089,11 @@ function file_get_file_references($file, $field = NULL, $age = FIELD_LOAD_REVISI
10821089
$query
10831090
->fieldCondition($file_field, 'fid', $file->fid)
10841091
->age($age);
1092+
if (!$check_access) {
1093+
// Neutralize the 'entity_field_access' query tag added by
1094+
// field_sql_storage_field_storage_query().
1095+
$query->addTag('DANGEROUS_ACCESS_CHECK_OPT_OUT');
1096+
}
10851097
$references[$field_name] = $query->execute();
10861098
}
10871099
}

modules/file/tests/file.test

+73
Original file line numberDiff line numberDiff line change
@@ -1626,6 +1626,79 @@ class FilePrivateTestCase extends FileFieldTestCase {
16261626
$this->drupalGet($file_url);
16271627
$this->assertResponse(403, 'Confirmed that another anonymous user cannot access the permanent file when it is referenced by an unpublished node.');
16281628
}
1629+
1630+
/**
1631+
* Tests file access for private nodes when file download access is granted.
1632+
*/
1633+
function testPrivateFileDownloadAccessGranted() {
1634+
// Tell file_module_test to attempt to grant access to all private files,
1635+
// and ensure that it is doing so correctly.
1636+
$test_file = $this->getTestFile('text');
1637+
$uri = file_unmanaged_move($test_file->uri, 'private://');
1638+
$file_url = file_create_url($uri);
1639+
$this->drupalGet($file_url);
1640+
$this->assertResponse(403, 'Access is not granted to an arbitrary private file by default.');
1641+
variable_set('file_module_test_grant_download_access', TRUE);
1642+
$this->drupalGet($file_url);
1643+
$this->assertResponse(200, 'Access is granted to an arbitrary private file after a module grants access to all private files in hook_file_download().');
1644+
1645+
// Create a public node with a file attached.
1646+
$type_name = 'page';
1647+
$field_name = strtolower($this->randomName());
1648+
$this->createFileField($field_name, $type_name, array('uri_scheme' => 'private'));
1649+
$test_file = $this->getTestFile('text');
1650+
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name, TRUE, array('private' => FALSE));
1651+
$node = node_load($nid, NULL, TRUE);
1652+
$file_url = file_create_url($node->{$field_name}[LANGUAGE_NONE][0]['uri']);
1653+
1654+
// Unpublish the node and ensure that only administrators (not anonymous
1655+
// users) can access the node and download the file; the expectation is
1656+
// that the File module's hook_file_download() implementation will deny
1657+
// access and thereby override the file_module_test module's access grant.
1658+
$node->status = NODE_NOT_PUBLISHED;
1659+
node_save($node);
1660+
$this->drupalLogin($this->admin_user);
1661+
$this->drupalGet("node/$nid");
1662+
$this->assertResponse(200, 'Administrator can access the unpublished node.');
1663+
$this->drupalGet($file_url);
1664+
$this->assertResponse(200, 'Administrator can download the file attached to the unpublished node.');
1665+
$this->drupalLogOut();
1666+
$this->drupalGet("node/$nid");
1667+
$this->assertResponse(403, 'Anonymous user cannot access the unpublished node.');
1668+
$this->drupalGet($file_url);
1669+
$this->assertResponse(403, 'Anonymous user cannot download the file attached to the unpublished node.');
1670+
1671+
// Re-publish the node and ensure that the node and file can be accessed by
1672+
// everyone.
1673+
$node->status = NODE_PUBLISHED;
1674+
node_save($node);
1675+
$this->drupalLogin($this->admin_user);
1676+
$this->drupalGet("node/$nid");
1677+
$this->assertResponse(200, 'Administrator can access the published node.');
1678+
$this->drupalGet($file_url);
1679+
$this->assertResponse(200, 'Administrator can download the file attached to the published node.');
1680+
$this->drupalLogOut();
1681+
$this->drupalGet("node/$nid");
1682+
$this->assertResponse(200, 'Anonymous user can access the published node.');
1683+
$this->drupalGet($file_url);
1684+
$this->assertResponse(200, 'Anonymous user can download the file attached to the published node.');
1685+
1686+
// Make the node private via the node access system and test that only
1687+
// administrators (not anonymous users) can access the node and download
1688+
// the file.
1689+
$node->private = TRUE;
1690+
node_save($node);
1691+
$this->drupalLogin($this->admin_user);
1692+
$this->drupalGet("node/$nid");
1693+
$this->assertResponse(200, 'Administrator can access the private node.');
1694+
$this->drupalGet($file_url);
1695+
$this->assertResponse(200, 'Administrator can download the file attached to the private node.');
1696+
$this->drupalLogOut();
1697+
$this->drupalGet("node/$nid");
1698+
$this->assertResponse(403, 'Anonymous user cannot access the private node.');
1699+
$this->drupalGet($file_url);
1700+
$this->assertResponse(403, 'Anonymous user cannot download the file attached to the private node.');
1701+
}
16291702
}
16301703

16311704
/**

modules/file/tests/file_module_test.module

+15
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,18 @@ function file_module_test_form_submit($form, &$form_state) {
6767
}
6868
drupal_set_message(t('The file id is %fid.', array('%fid' => $fid)));
6969
}
70+
71+
/**
72+
* Implements hook_file_download().
73+
*/
74+
function file_module_test_file_download($uri) {
75+
if (variable_get('file_module_test_grant_download_access')) {
76+
// Mimic what file_get_content_headers() would do if we had a full $file
77+
// object to pass to it.
78+
return array(
79+
'Content-Type' => mime_header_encode(file_get_mimetype($uri)),
80+
'Content-Length' => filesize($uri),
81+
'Cache-Control' => 'private',
82+
);
83+
}
84+
}

modules/simpletest/tests/common.test

+33-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class DrupalAlterTestCase extends DrupalWebTestCase {
7676
class CommonURLUnitTest extends DrupalWebTestCase {
7777
public static function getInfo() {
7878
return array(
79-
'name' => 'URL generation tests',
79+
'name' => 'URL generation unit tests',
8080
'description' => 'Confirm that url(), drupal_get_query_parameters(), drupal_http_build_query(), and l() work correctly with various input.',
8181
'group' => 'System',
8282
);
@@ -372,6 +372,38 @@ class CommonURLUnitTest extends DrupalWebTestCase {
372372
}
373373
}
374374

375+
/**
376+
* Web tests for URL generation functions.
377+
*/
378+
class CommonURLWebTest extends DrupalWebTestCase {
379+
public static function getInfo() {
380+
return array(
381+
'name' => 'URL generation web tests',
382+
'description' => 'Confirm that URL-generating functions work correctly on specific site paths.',
383+
'group' => 'System',
384+
);
385+
}
386+
387+
function setUp() {
388+
parent::setUp('common_test');
389+
}
390+
391+
/**
392+
* Tests the url() function on internal paths which mimic external URLs.
393+
*/
394+
function testInternalPathMimicsExternal() {
395+
// Ensure that calling url(current_path()) on "/http://example.com" (an
396+
// internal path which mimics an external URL) always links to the internal
397+
// path, not the external URL. This helps protect against external URL link
398+
// injection vulnerabilities.
399+
variable_set('common_test_link_to_current_path', TRUE);
400+
$this->drupalGet('/http://example.com');
401+
$this->clickLink('link which should point to the current path');
402+
$this->assertUrl('/http://example.com');
403+
$this->assertText('link which should point to the current path');
404+
}
405+
}
406+
375407
/**
376408
* Tests url_is_external().
377409
*/

modules/simpletest/tests/common_test.module

+3
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ function common_test_init() {
9999
if (variable_get('common_test_redirect_current_path', FALSE)) {
100100
drupal_goto(current_path());
101101
}
102+
if (variable_get('common_test_link_to_current_path', FALSE)) {
103+
drupal_set_message(l('link which should point to the current path', current_path()));
104+
}
102105
}
103106

104107
/**

0 commit comments

Comments
 (0)