diff --git a/web/.htaccess b/web/.htaccess index c5dbb5b4c..8ef4c4981 100644 --- a/web/.htaccess +++ b/web/.htaccess @@ -58,6 +58,16 @@ DirectoryIndex index.php php_value mbstring.encoding_translation 0 +# PHP 7, Apache 1 and 2. + + php_value magic_quotes_gpc 0 + php_value register_globals 0 + php_value session.auto_start 0 + php_value mbstring.http_input pass + php_value mbstring.http_output pass + php_value mbstring.encoding_translation 0 + + # Requires mod_expires to be enabled. # Enable expirations. diff --git a/web/CHANGELOG.txt b/web/CHANGELOG.txt index e6b38ab8b..6c49315ac 100644 --- a/web/CHANGELOG.txt +++ b/web/CHANGELOG.txt @@ -1,23 +1,39 @@ +Drupal 6.38-p6, 2018-12-28 - SA-CORE-2018-006 +--------------------------------------------- + +Code brought up-to-date with Pressflow 6.46.126 and Drupal 6.46 LTS, including +a backport of the security fix for an open redirect exploit. + +The path module allows users with the 'administer paths' to create pretty URLs +for content. In certain circumstances the user can enter a particular path that +triggers an open redirect to a malicious url. The issue is mitigated by the fact +that the user needs the administer paths permission to exploit. + +Upstream reference: +https://www.drupal.org/sa-core-2018-006 + + Drupal 6.38-p5, 2018-04-25 - SA-CORE-2018-004 +--------------------------------------------- A remote code execution vulnerability may exist within multiple subsystems of Drupal 6. This potentially allows attackers to exploit multiple attack vectors on a Drupal site, which could result in the site being compromised. This vulnerability is related to Drupal core - Highly critical - Remote Code -Execution - SA-CORE-2018-002 (fixed by Tag1 Quo -p3 and -p4). While -SA-CORE-2018-002 is being exploited in the wild, there are not yet any known -exploits for SA-CORE-2018-004. +Execution - SA-CORE-2018-002 (fixed by Tag1 Quo -p3 and -p4). Upstream reference: https://www.drupal.org/sa-core-2018-004 Drupal 6.38-p4, 2018-03-28 - SA-CORE-2018-002 +--------------------------------------------- A bugfix introduced in -p3, affecting sites using PHP 5.x. Drupal 6.38-p3, 2018-03-28 - SA-CORE-2018-002 +--------------------------------------------- A remote code execution vulnerability exists within multiple subsystems. This potentially allows attackers to exploit multiple attack vectors on a @@ -71,6 +87,7 @@ Almost all site administrators will want to take the infrastructure actions suggested in httppoxy.org and a complete solution to this vulnerability. + Drupal 6.38, 2016-02-24 - Final release --------------------------------------- - Fixed security issues (multiple vulnerabilities). See SA-CORE-2016-001. diff --git a/web/includes/bootstrap.inc b/web/includes/bootstrap.inc index 6d695b173..9ad12f386 100644 --- a/web/includes/bootstrap.inc +++ b/web/includes/bootstrap.inc @@ -790,7 +790,7 @@ function drupal_page_is_cacheable($force = NULL) { $result = $forced_cache && !drupal_session_started() && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') - && !count(drupal_get_messages(NULL, FALSE)) + && !drupal_get_messages(NULL, FALSE) && !drupal_is_cli(); return $result; @@ -1921,7 +1921,12 @@ function drupal_session_regenerate() { if (drupal_session_started()) { $old_session_id = session_id(); + // On PHP7, sess_read($key) is being called with the new session key but not + // on PHP5. This failsafe stores the initial user object, which on PHP5 should + // be just the same, and restore it later on. + $account = $user; session_regenerate_id(); + $user = $account; } else { // Start the session when it doesn't exist yet. @@ -2404,3 +2409,30 @@ function _drupal_parse_url($url) { } return $options; } + +// Shim for ereg() family of functions for PHP 7+ where they don't exist. +if (!function_exists('ereg')) { + function ereg($pattern, $subject, &$matches = array()) { + return preg_match('/' . $pattern . '/', $subject, $matches); + } + + function eregi($pattern, $subject, &$matches = array()) { + return preg_match('/' . $pattern . '/i', $subject, $matches); + } + + function ereg_replace($pattern, $replacement, $string) { + return preg_replace('/' . $pattern . '/', $replacement, $string); + } + + function eregi_replace($pattern, $replacement, $string) { + return preg_replace('/' . $pattern . '/i', $replacement, $string); + } + + function split($pattern, $subject, $limit = -1) { + return preg_split('/' . $pattern . '/', $subject, $limit); + } + + function spliti($pattern, $subject, $limit = -1) { + return preg_split('/' . $pattern . '/i', $subject, $limit); + } +} diff --git a/web/includes/common.inc b/web/includes/common.inc index b3e0949fe..d37016360 100644 --- a/web/includes/common.inc +++ b/web/includes/common.inc @@ -1540,6 +1540,10 @@ function url($path = NULL, $options = array()) { } elseif (!empty($path) && !$options['alias']) { $path = drupal_get_path_alias($path, isset($options['language']) ? $options['language']->language : ''); + // Strip leading slashes from internal paths to prevent them becoming external + // URLs without protocol. /example.com should not be turned into + // //example.com. + $path = ltrim($path, '/'); } if (function_exists('custom_url_rewrite_outbound')) { diff --git a/web/includes/file.inc b/web/includes/file.inc index 96a140d91..bfa358389 100644 --- a/web/includes/file.inc +++ b/web/includes/file.inc @@ -236,6 +236,9 @@ SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006 php_flag engine off + + php_flag engine off + # PHP 4, Apache 1. php_flag engine off diff --git a/web/includes/form.inc b/web/includes/form.inc index b9e066d6b..be58955e2 100644 --- a/web/includes/form.inc +++ b/web/includes/form.inc @@ -737,7 +737,7 @@ function _form_validate($elements, &$form_state, $form_id = NULL) { // A simple call to empty() will not cut it here as some fields, like // checkboxes, can return a valid value of '0'. Instead, check the // length if it's a string, and the item count if it's an array. - if ($elements['#required'] && (!count($elements['#value']) || (is_string($elements['#value']) && strlen(trim($elements['#value'])) == 0))) { + if ($elements['#required'] && (is_array($elements['#value']) && !count($elements['#value']) || (is_string($elements['#value']) && strlen(trim($elements['#value'])) == 0))) { form_error($elements, $t('!name field is required.', array('!name' => $elements['#title']))); } @@ -1426,6 +1426,12 @@ function form_set_value($form_item, $value, &$form_state) { function _form_set_value(&$form_values, $form_item, $parents, $value) { $parent = array_shift($parents); if (empty($parents)) { + // This makes PHP 7 have the same behavior as PHP 5 when the value is an + // empty string, rather than an array. This is depended on surprisingly + // often in Drupal 6 contrib. + if ($form_values === '') { + $form_values = array(); + } $form_values[$parent] = $value; } else { diff --git a/web/includes/install.inc b/web/includes/install.inc index 171ba736c..cb149ceec 100644 --- a/web/includes/install.inc +++ b/web/includes/install.inc @@ -236,6 +236,11 @@ function drupal_rewrite_settings($settings = array(), $prefix = '') { if ($fp && fwrite($fp, $buffer) === FALSE) { drupal_set_message(st('Failed to modify %settings, please verify the file permissions.', array('%settings' => $settings_file)), 'error'); } + fclose($fp); + // Invalidate the cache of the settings file so next request read the newly modified one + if (function_exists('opcache_invalidate')) { + opcache_invalidate($settings_file, TRUE); + } } else { drupal_set_message(st('Failed to open %settings, please verify the file permissions.', array('%settings' => $default_settings)), 'error'); diff --git a/web/includes/install.mysql.inc b/web/includes/install.mysql.inc index 73aa365b0..d08e72ba6 100644 --- a/web/includes/install.mysql.inc +++ b/web/includes/install.mysql.inc @@ -31,6 +31,7 @@ function drupal_test_mysql($url, &$success) { $url['pass'] = isset($url['pass']) ? urldecode($url['pass']) : ''; $url['host'] = urldecode($url['host']); $url['path'] = urldecode($url['path']); + $url['port'] = isset($url['port']) ? $url['port'] : NULL; // Allow for non-standard MySQL port. if (isset($url['port'])) { diff --git a/web/includes/install.mysqli.inc b/web/includes/install.mysqli.inc index 008080416..ad49ab28f 100644 --- a/web/includes/install.mysqli.inc +++ b/web/includes/install.mysqli.inc @@ -31,6 +31,7 @@ function drupal_test_mysqli($url, &$success) { $url['pass'] = isset($url['pass']) ? urldecode($url['pass']) : ''; $url['host'] = urldecode($url['host']); $url['path'] = urldecode($url['path']); + $url['port'] = isset($url['port']) ? $url['port'] : NULL; $connection = mysqli_init(); @mysqli_real_connect($connection, $url['host'], $url['user'], $url['pass'], substr($url['path'], 1), $url['port'], NULL, MYSQLI_CLIENT_FOUND_ROWS); diff --git a/web/includes/unicode.inc b/web/includes/unicode.inc index b1ce75760..bf0ae52c9 100644 --- a/web/includes/unicode.inc +++ b/web/includes/unicode.inc @@ -317,6 +317,25 @@ function _mime_header_decode($matches) { return $data; } +/** + * Simple class to use instead of closure in decode_entities(). + * + * @see decode_entities() + */ +class DrupalDecodeEntitiesCallback { + public $html_entities; + public $exclude; + + public function __construct($html_entities, $exclude) { + $this->html_entities = $html_entities; + $this->exclude = $exclude; + } + + public function callback($matches) { + return _decode_entities($matches[1], $matches[2], $matches[0], $this->html_entities, $this->exclude); + } +} + /** * Decodes all HTML entities (including numerical ones) to regular UTF-8 bytes. * @@ -342,11 +361,12 @@ function decode_entities($text, $exclude = array()) { // Flip the exclude list so that we can do quick lookups later. $exclude = array_flip($exclude); + // Use object instead of closure to retain PHP 5.2 compatibility. + $callback = new DrupalDecodeEntitiesCallback($html_entities, $exclude); + // Use a regexp to select all entities in one pass, to avoid decoding - // double-escaped entities twice. The PREG_REPLACE_EVAL modifier 'e' is - // being used to allow for a callback (see - // http://php.net/manual/en/reference.pcre.pattern.modifiers). - return preg_replace('/&(#x?)?([A-Za-z0-9]+);/e', '_decode_entities("$1", "$2", "$0", $html_entities, $exclude)', $text); + // double-escaped entities twice. + return preg_replace_callback('/&(#x?)?([A-Za-z0-9]+);/', array($callback, 'callback'), $text); } /** diff --git a/web/install.php b/web/install.php index 8aea0eacc..9a7821658 100644 --- a/web/install.php +++ b/web/install.php @@ -655,7 +655,7 @@ function install_tasks($profile, $task) { global $base_url, $install_locale; // Bootstrap newly installed Drupal, while preserving existing messages. - $messages = isset($_SESSION['messages']) ? $_SESSION['messages'] : ''; + $messages = isset($_SESSION['messages']) ? $_SESSION['messages'] : array(); drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); $_SESSION['messages'] = $messages; diff --git a/web/modules/openid/openid.install b/web/modules/openid/openid.install index 8f1013621..0b0c95d29 100644 --- a/web/modules/openid/openid.install +++ b/web/modules/openid/openid.install @@ -142,6 +142,68 @@ function openid_update_6000() { return $ret; } +/** + * Bind associations to their providers. + */ +function openid_update_6001() { + $ret = array(); + + db_drop_table($ret, 'openid_association'); + + $schema['openid_association'] = array( + 'description' => 'Stores temporary shared key association information for OpenID authentication.', + 'fields' => array( + 'idp_endpoint_uri' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'description' => 'Primary Key: URI of the OpenID Provider endpoint.', + ), + 'assoc_handle' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'description' => 'Used to refer to this association in subsequent messages.', + ), + 'assoc_type' => array( + 'type' => 'varchar', + 'length' => 32, + 'description' => 'The signature algorithm used: one of HMAC-SHA1 or HMAC-SHA256.', + ), + 'session_type' => array( + 'type' => 'varchar', + 'length' => 32, + 'description' => 'Valid association session types: "no-encryption", "DH-SHA1", and "DH-SHA256".', + ), + 'mac_key' => array( + 'type' => 'varchar', + 'length' => 255, + 'description' => 'The MAC key (shared secret) for this association.', + ), + 'created' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => 'UNIX timestamp for when the association was created.', + ), + 'expires_in' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => 'The lifetime, in seconds, of this association.', + ), + ), + 'primary key' => array('idp_endpoint_uri'), + 'unique keys' => array( + 'assoc_handle' => array('assoc_handle'), + ), + ); + + db_create_table($ret, 'openid_association', $schema['openid_association']); + + return $ret; +} + /** * @} End of "addtogroup updates-6.x-extra". * The next series of updates should start at 7000. diff --git a/web/modules/statistics/statistics.module b/web/modules/statistics/statistics.module index ba964ef15..b15380e2c 100644 --- a/web/modules/statistics/statistics.module +++ b/web/modules/statistics/statistics.module @@ -176,7 +176,7 @@ function statistics_user($op, &$edit, &$user) { * Implementation of hook_cron(). */ function statistics_cron() { - $statistics_timestamp = variable_get('statistics_day_timestamp', ''); + $statistics_timestamp = variable_get('statistics_day_timestamp', 0); if ((time() - $statistics_timestamp) >= 86400) { // Reset day counts. diff --git a/web/modules/system/system.module b/web/modules/system/system.module index bbe3f7754..7826a21f1 100644 --- a/web/modules/system/system.module +++ b/web/modules/system/system.module @@ -8,7 +8,7 @@ /** * The current system version. */ -define('VERSION', '6.38-p5'); +define('VERSION', '6.46'); /** * Core API compatibility. diff --git a/web/modules/taxonomy/taxonomy.pages.inc b/web/modules/taxonomy/taxonomy.pages.inc index 99757b169..56460c308 100644 --- a/web/modules/taxonomy/taxonomy.pages.inc +++ b/web/modules/taxonomy/taxonomy.pages.inc @@ -29,6 +29,7 @@ function taxonomy_term_page($str_tids = '', $depth = 0, $op = 'page') { switch ($op) { case 'page': + $current = new stdClass(); // Build breadcrumb based on first hierarchy of first term: $current->tid = $tids[0]; $breadcrumb = array(); diff --git a/web/modules/upload/upload.module b/web/modules/upload/upload.module index ee5b127dd..cb48c9648 100644 --- a/web/modules/upload/upload.module +++ b/web/modules/upload/upload.module @@ -278,7 +278,7 @@ function upload_nodeapi(&$node, $op, $teaser = NULL) { case 'load': $output = ''; if (variable_get("upload_$node->type", 1) == 1) { - $output['files'] = upload_load($node); + $output = array('files' => upload_load($node)); return $output; } break; diff --git a/web/sites/default/default.settings.php b/web/sites/default/default.settings.php index 497bf1112..fc905a8fa 100644 --- a/web/sites/default/default.settings.php +++ b/web/sites/default/default.settings.php @@ -160,7 +160,6 @@ ini_set('session.cache_limiter', 'none'); ini_set('session.cookie_lifetime', 2000000); ini_set('session.gc_maxlifetime', 200000); -ini_set('session.save_handler', 'user'); ini_set('session.use_cookies', 1); ini_set('session.use_only_cookies', 1); ini_set('session.use_trans_sid', 0);