diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 0f40a18595babcbab4e41d771dbb1f2e0c5fea0c..5ee63790c4b68fe74b5fc0c5d084f3049e31d94b 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,8 +1,10 @@ // $Id$ -Drupal 6.5-dev, xxxx-xx-xx (development release) +Drupal 6.5, 2008-10-08 ---------------------- - +- Fixed security issues, (File upload access bypass, Access rules bypass, + BlogAPI access bypass), see SA-2008-060. +- Fixed a variety of small bugs. Drupal 6.4, 2008-08-13 ---------------------- @@ -135,6 +137,12 @@ Drupal 6.0, 2008-02-13 - Removed old system updates. Updates from Drupal versions prior to 5.x will require upgrading to 5.x before upgrading to 6.x. +Drupal 5.11, 2008-10-08 +----------------------- +- fixed a variety of small bugs. +- fixed security issues, (File upload access bypass, Access rules bypass, + BlogAPI access bypass, Node validation bypass), see SA-2008-060 + Drupal 5.10, 2008-08-13 ----------------------- - fixed a variety of small bugs. diff --git a/includes/common.inc b/includes/common.inc index 445261fdc1724d98480d02b55f356f8726326648..9488bb92531965836ad6eef35abf497ef1d64d4e 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -587,7 +587,7 @@ function drupal_error_handler($errno, $message, $filename, $line, $context) { return; } - if ($errno & (E_ALL)) { + if ($errno & (E_ALL ^ E_NOTICE)) { $types = array(1 => 'error', 2 => 'warning', 4 => 'parse error', 8 => 'notice', 16 => 'core error', 32 => 'core warning', 64 => 'compile error', 128 => 'compile warning', 256 => 'user error', 512 => 'user warning', 1024 => 'user notice', 2048 => 'strict warning', 4096 => 'recoverable fatal error'); // For database errors, we want the line number/file name of the place that diff --git a/modules/blogapi/blogapi.module b/modules/blogapi/blogapi.module index 730e7fb28f0355a4b1bdc3c0385b14e60603a25f..d5101d5e1e1970a74da2f08e02f65930abb08915 100644 --- a/modules/blogapi/blogapi.module +++ b/modules/blogapi/blogapi.module @@ -222,6 +222,11 @@ function blogapi_blogger_new_post($appkey, $blogid, $username, $password, $conte node_invoke_nodeapi($edit, 'blogapi new'); + $valid = blogapi_status_error_check($edit, $publish); + if ($valid !== TRUE) { + return $valid; + } + node_validate($edit); if ($errors = form_get_errors()) { return blogapi_error(implode("\n", $errors)); @@ -259,7 +264,8 @@ function blogapi_blogger_edit_post($appkey, $postid, $username, $password, $cont if (!node_access('update', $node)) { return blogapi_error(t('You do not have permission to update this post.')); } - + // Save the original status for validation of permissions. + $original_status = $node->status; $node->status = $publish; // check for bloggerAPI vs. metaWeblogAPI @@ -275,6 +281,11 @@ function blogapi_blogger_edit_post($appkey, $postid, $username, $password, $cont node_invoke_nodeapi($node, 'blogapi edit'); + $valid = blogapi_status_error_check($node, $original_status); + if ($valid !== TRUE) { + return $valid; + } + node_validate($node); if ($errors = form_get_errors()) { return blogapi_error(implode("\n", $errors)); @@ -307,6 +318,33 @@ function blogapi_blogger_get_post($appkey, $postid, $username, $password) { return _blogapi_get_post($node, TRUE); } +/** + * Check that the user has permission to save the node with the chosen status. + * + * @return + * TRUE if no error, or the blogapi_error(). + */ +function blogapi_status_error_check($node, $original_status) { + + $node = (object) $node; + + $node_type_default = variable_get('node_options_'. $node->type, array('status', 'promote')); + + // If we don't have the 'administer nodes' permission and the status is + // changing or for a new node the status is not the content type's default, + // then return an error. + if (!user_access('administer nodes') && (($node->status != $original_status) || (empty($node->nid) && $node->status != in_array('status', $node_type_default)))) { + if ($node->status) { + return blogapi_error(t('You do not have permission to publish this type of post. Please save it as a draft instead.')); + } + else { + return blogapi_error(t('You do not have permission to save this post as a draft. Please publish it instead.')); + } + } + return TRUE; +} + + /** * Blogging API callback. Removes the specified blog node. */ @@ -514,10 +552,58 @@ function blogapi_mt_set_post_categories($postid, $username, $password, $categori foreach ($categories as $category) { $node->taxonomy[] = $category['categoryId']; } + $validated = blogapi_mt_validate_terms($node); + if ($validated !== TRUE) { + return $validated; + } node_save($node); return TRUE; } +/** + * Blogging API helper - find allowed taxonomy terms for a node type. + */ +function blogapi_mt_validate_terms($node) { + // We do a lot of heavy lifting here since taxonomy module doesn't have a + // stand-alone validation function. + if (module_exists('taxonomy')) { + $found_terms = array(); + if (!empty($node->taxonomy)) { + $term_list = array_unique($node->taxonomy); + $params = $term_list; + $params[] = $node->type; + $result = db_query(db_rewrite_sql("SELECT t.tid, t.vid FROM {term_data} t INNER JOIN {vocabulary_node_types} n ON t.vid = n.vid WHERE t.tid IN (". db_placeholders($term_list) .") AND n.type = '%s'", 't', 'tid'), $params); + $found_terms = array(); + $found_count = 0; + while ($term = db_fetch_object($result)) { + $found_terms[$term->vid][$term->tid] = $term->tid; + $found_count++; + } + // If the counts don't match, some terms are invalid or not accessible to this user. + if (count($term_list) != $found_count) { + return blogapi_error(t('Invalid categories submitted.')); + } + } + // Look up all the vocabularies for this node type. + $result2 = db_query(db_rewrite_sql("SELECT v.vid, v.name, v.required, v.multiple FROM {vocabulary} v INNER JOIN {vocabulary_node_types} n ON v.vid = n.vid WHERE n.type = '%s'", 'v', 'vid'), $node->type); + // Check each vocabulary associated with this node type. + while ($vocabulary = db_fetch_object($result2)) { + // Required vocabularies must have at least one term. + if ($vocabulary->required && empty($found_terms[$vocabulary->vid])) { + return blogapi_error(t('A category from the @vocabulary_name vocabulary is required.', array('@vocabulary_name' => $vocabulary->name))); + } + // Vocabularies that don't allow multiple terms may have at most one. + if (!($vocabulary->multiple) && (isset($found_terms[$vocabulary->vid]) && count($found_terms[$vocabulary->vid]) > 1)) { + return blogapi_error(t('You may only choose one category from the @vocabulary_name vocabulary.'), array('@vocabulary_name' => $vocabulary->name)); + } + } + } + elseif (!empty($node->taxonomy)) { + return blogapi_error(t('Error saving categories. This feature is not available.')); + } + return TRUE; +} + /** * Blogging API callback. Sends a list of available input formats. */ @@ -549,11 +635,16 @@ function blogapi_mt_publish_post($postid, $username, $password) { return blogapi_error(t('Invalid post.')); } - $node->status = 1; - if (!node_access('update', $node)) { + // Nothing needs to be done if already published. + if ($node->status) { + return; + } + + if (!node_access('update', $node) || !user_access('administer nodes')) { return blogapi_error(t('You do not have permission to update this post.')); } + $node->status = 1; node_save($node); return TRUE; diff --git a/modules/system/system.module b/modules/system/system.module index 94e34ae22a018f5a980aa6673ad72aca433b2450..cfb857ae7a283c843692d927ce713fb16cd43bd0 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -9,7 +9,7 @@ /** * The current system version. */ -define('VERSION', '6.5-dev'); +define('VERSION', '6.5'); /** * Core API compatibility. diff --git a/modules/upload/upload.module b/modules/upload/upload.module index cf0420094eb13400d04b1f667c8d4435d6414a89..2c5ab4405999aadf2c3eba7c3e38e94d1d72b70c 100644 --- a/modules/upload/upload.module +++ b/modules/upload/upload.module @@ -178,7 +178,7 @@ function upload_node_form_submit(&$form, &$form_state) { ); // Save new file uploads. - if (($user->uid != 1 || user_access('upload files')) && ($file = file_save_upload('upload', $validators, file_directory_path()))) { + if (user_access('upload files') && ($file = file_save_upload('upload', $validators, file_directory_path()))) { $file->list = variable_get('upload_list_default', 1); $file->description = $file->filename; $file->weight = 0; diff --git a/modules/user/user.module b/modules/user/user.module index b2abaed474f2189e83b2a49ee73a11ccd9867e45..33c81f6fe2c0edd179173499b8ff69c380e6acb7 100644 --- a/modules/user/user.module +++ b/modules/user/user.module @@ -1334,9 +1334,18 @@ function user_login_final_validate($form, &$form_state) { function user_authenticate($form_values = array()) { global $user; + // Load the account to check if the e-mail is denied by an access rule. + // Doing this check here saves us a user_load() in user_login_name_validate() + // and introduces less code change for a security fix. + $account = user_load(array('name' => $form_values['name'], 'pass' => trim($form_values['pass']), 'status' => 1)); + if ($account && drupal_is_denied('mail', $account->mail)) { + form_set_error('name', t('The name %name is registered using a reserved e-mail address and therefore could not be logged in.', array('%name' => $account->name))); + } + // Name and pass keys are required. - if (!empty($form_values['name']) && !empty($form_values['pass']) && - $account = user_load(array('name' => $form_values['name'], 'pass' => trim($form_values['pass']), 'status' => 1))) { + // The user is about to be logged in, so make sure no error was previously + // encountered in the validation process. + if (!form_get_errors() && !empty($form_values['name']) && !empty($form_values['pass']) && $account) { $user = $account; user_authenticate_finalize($form_values); return $user; diff --git a/modules/user/user.pages.inc b/modules/user/user.pages.inc index e2c1781369d82f939507e9baac2c69d925d56364..f74db1d151f4aca54c727a0618a3ba6fb309e835 100644 --- a/modules/user/user.pages.inc +++ b/modules/user/user.pages.inc @@ -43,6 +43,13 @@ function user_pass() { function user_pass_validate($form, &$form_state) { $name = trim($form_state['values']['name']); + + // Blocked accounts cannot request a new password, + // check provided username and email against access rules. + if (drupal_is_denied('user', $name) || drupal_is_denied('mail', $name)) { + form_set_error('name', t('%name is not allowed to request a new password.', array('%name' => $name))); + } + // Try to load by email. $account = user_load(array('mail' => $name, 'status' => 1)); if (!$account) { @@ -87,6 +94,12 @@ function user_pass_reset(&$form_state, $uid, $timestamp, $hashed_pass, $action = $current = time(); // Some redundant checks for extra security ? if ($timestamp < $current && $account = user_load(array('uid' => $uid, 'status' => 1)) ) { + // Deny one-time login to blocked accounts. + if (drupal_is_denied('user', $account->name) || drupal_is_denied('mail', $account->mail)) { + drupal_set_message(t('You have tried to use a one-time login for an account which has been blocked.'), 'error'); + drupal_goto(); + } + // No time out for first time login. if ($account->login && $current - $timestamp > $timeout) { drupal_set_message(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'));