diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 4fd00173a35f774db8cd97ffb121b8aaf5410fd3..6c2c353eb7b9901ea0f5260fbc4da6117e9a9f13 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,7 +1,11 @@ -Drupal 6.37-dev, xxxx-xx-xx (development release) +Drupal 6.38-dev, xxxx-xx-xx (development release) ---------------------- +Drupal 6.37, 2015-08-19 +----------------------- +- Fixed security issues (multiple vulnerabilities). See SA-CORE-2015-003. + Drupal 6.36, 2015-06-17 ----------------------- - Fixed security issues (OpenID impersonation). See SA-CORE-2015-002. diff --git a/includes/form.inc b/includes/form.inc index e9ac8e40fe8eea4ce9de634c58adaad00b5b45d2..b55d724a8d7f9a9e9ab18f6743cab97b3da1780a 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -626,7 +626,7 @@ function drupal_validate_form($form_id, $form, &$form_state) { // If the session token was set by drupal_prepare_form(), ensure that it // matches the current user's session. if (isset($form['#token'])) { - if (!drupal_valid_token($form_state['values']['form_token'], $form['#token'])) { + if (!drupal_valid_token($form_state['values']['form_token'], $form['#token']) || !empty($form_state['invalid_token'])) { // Setting this error will cause the form to fail validation. form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.')); @@ -926,12 +926,28 @@ function form_builder($form_id, $form, &$form_state) { $form += $info; } + // Special handling if we're on the top level form element. if (isset($form['#type']) && $form['#type'] == 'form') { $cache = NULL; $complete_form = $form; if (!empty($form['#programmed'])) { $form_state['submitted'] = TRUE; } + else { + // If the session token was set by drupal_prepare_form(), ensure that it + // matches the current user's session before processing input. + if (isset($form['#token']) && isset($form['#post']) && (isset($form['#post']['form_id']) && $form['#post']['form_id'] == $form_id)) { + $form_state['invalid_token'] = FALSE; + if (empty($form['#post']['form_token']) || !drupal_valid_token($form['#post']['form_token'], $form['#token'])) { + // Setting this error will cause the form to fail validation. + form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.')); + // This value is checked in _form_builder_handle_input_element(). + $form_state['invalid_token'] = TRUE; + // Make sure file uploads do not get processed. + $_FILES = array(); + } + } + } } if (isset($form['#input']) && $form['#input']) { @@ -1019,6 +1035,15 @@ function form_builder($form_id, $form, &$form_state) { * attached to a specific element. */ function _form_builder_handle_input_element($form_id, &$form, &$form_state, $complete_form) { + static $safe_core_value_callbacks = array( + 'form_type_token_value', + 'form_type_textfield_value', + 'form_type_checkbox_value', + 'form_type_checkboxes_value', + 'form_type_password_confirm_value', + 'form_type_select_value' + ); + if (!isset($form['#name'])) { $name = array_shift($form['#parents']); $form['#name'] = $name; @@ -1051,7 +1076,14 @@ function _form_builder_handle_input_element($form_id, &$form, &$form_state, $com if (!$form['#programmed'] || isset($edit)) { // Call #type_value to set the form value; if (function_exists($function)) { - $form['#value'] = $function($form, $edit); + // Skip all value callbacks except safe ones like text if the CSRF + // token was invalid. + if (empty($form_state['invalid_token']) || in_array($function, $safe_core_value_callbacks)) { + $form['#value'] = $function($form, $edit); + } + else { + $edit = NULL; + } } if (!isset($form['#value']) && isset($edit)) { $form['#value'] = $edit; @@ -2078,6 +2110,29 @@ function theme_token($element) { return theme('hidden', $element); } +/** + * Process function to prepare autocomplete data. + * + * @param $element + * A textfield or other element with a #autocomplete_path. + * + * @return array + * The processed form element. + */ +function form_process_autocomplete($element) { + $element['#autocomplete_input'] = array(); + if ($element['#autocomplete_path'] && menu_valid_path(array('link_path' => $element['#autocomplete_path']))) { + $element['#autocomplete_input']['#id'] = $element['#id'] .'-autocomplete'; + // Force autocomplete to use non-clean URLs since this protects against the + // browser interpreting the path plus search string as an actual file. + $current_clean_url = isset($GLOBALS['conf']['clean_url']) ? $GLOBALS['conf']['clean_url'] : NULL; + $GLOBALS['conf']['clean_url'] = 0; + $element['#autocomplete_input']['#url_value'] = check_url(url($element['#autocomplete_path'], array('absolute' => TRUE))); + $GLOBALS['conf']['clean_url'] = $current_clean_url; + } + return $element; +} + /** * Format a textfield. * @@ -2096,10 +2151,10 @@ function theme_textfield($element) { $extra = ''; $output = ''; - if ($element['#autocomplete_path'] && menu_valid_path(array('link_path' => $element['#autocomplete_path']))) { + if ($element['#autocomplete_path'] && !empty($element['#autocomplete_input'])) { drupal_add_js('misc/autocomplete.js'); $class[] = 'form-autocomplete'; - $extra = ''; + $extra = ''; } _form_set_class($element, $class); diff --git a/includes/menu.inc b/includes/menu.inc index 0d1ec258e8c2a8f880e329b8ee44826da8bbf30c..ef27d91e7791f958d6fd56416703ed5a004c9469 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -1002,7 +1002,7 @@ function menu_tree_collect_node_links(&$tree, &$node_links) { */ function menu_tree_check_access(&$tree, $node_links = array()) { - if ($node_links) { + if ($node_links && (user_access('access content') || user_access('bypass node access'))) { // Use db_rewrite_sql to evaluate view access without loading each full node. $nids = array_keys($node_links); $placeholders = '%d'. str_repeat(', %d', count($nids) - 1); diff --git a/misc/autocomplete.js b/misc/autocomplete.js index 8d0dcbe272192d5842c78e78828d5edf5eedeeaf..c5176727c48418305fb42ba089cedb6e653a9c4e 100644 --- a/misc/autocomplete.js +++ b/misc/autocomplete.js @@ -253,6 +253,16 @@ Drupal.ACDB.prototype.search = function (searchString) { var db = this; this.searchString = searchString; + // See if this string needs to be searched for anyway. The pattern ../ is + // stripped since it may be misinterpreted by the browser. + searchString = searchString.replace(/^\s+|\.{2,}\/|\s+$/g, ''); + // Skip empty search strings, or search strings ending with a comma, since + // that is the separator between search terms. + if (searchString.length <= 0 || + searchString.charAt(searchString.length - 1) == ',') { + return; + } + // See if this key has been searched for before if (this.cache[searchString]) { return this.owner.found(this.cache[searchString]); diff --git a/modules/system/system.module b/modules/system/system.module index 6ffa529c4c595159c4d1041ba5ae106754b856b4..ccc638fc7afb4e965af0e20f999e85ded8c8ca41 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -8,7 +8,7 @@ /** * The current system version. */ -define('VERSION', '6.37-dev'); +define('VERSION', '6.38-dev'); /** * Core API compatibility. @@ -169,7 +169,7 @@ function system_elements() { $type['submit'] = array('#input' => TRUE, '#name' => 'op', '#button_type' => 'submit', '#executes_submit_callback' => TRUE, '#process' => array('form_expand_ahah')); $type['button'] = array('#input' => TRUE, '#name' => 'op', '#button_type' => 'submit', '#executes_submit_callback' => FALSE, '#process' => array('form_expand_ahah')); $type['image_button'] = array('#input' => TRUE, '#button_type' => 'submit', '#executes_submit_callback' => TRUE, '#process' => array('form_expand_ahah'), '#return_value' => TRUE, '#has_garbage_value' => TRUE, '#src' => NULL); - $type['textfield'] = array('#input' => TRUE, '#size' => 60, '#maxlength' => 128, '#autocomplete_path' => FALSE, '#process' => array('form_expand_ahah')); + $type['textfield'] = array('#input' => TRUE, '#size' => 60, '#maxlength' => 128, '#autocomplete_path' => FALSE, '#process' => array('form_process_autocomplete', 'form_expand_ahah')); $type['password'] = array('#input' => TRUE, '#size' => 60, '#maxlength' => 128, '#process' => array('form_expand_ahah')); $type['password_confirm'] = array('#input' => TRUE, '#process' => array('expand_password_confirm')); $type['textarea'] = array('#input' => TRUE, '#cols' => 60, '#rows' => 5, '#resizable' => TRUE, '#process' => array('form_expand_ahah'));