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'));