diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 919e6fd27059b1f5dd55030a48d6d6c6c38c7f20..c14633db355d92e96950ac31db62ff3e87319dce 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,5 +1,11 @@ // $Id$ +Drupal 4.7.4, 2006-10-18 +------------------------ +- fixed security issue (XSS), see SA-2006-024 +- fixed security issue (CSRF), see SA-2006-025 +- fixed security issue (Form action attribute injection), see SA-2006-026 + Drupal 4.7.3, 2006-08-02 ------------------------ - fixed security issue (XSS), see SA-2006-011 diff --git a/includes/common.inc b/includes/common.inc index ebddac8e510e35d9dc73c292b802b605e797f767..ea1d96e7b9e78c6b2a0922d009e107660a33aaf3 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -980,8 +980,9 @@ function url($path = NULL, $query = NULL, $fragment = NULL, $absolute = FALSE) { } // Return an external link if $path contains an allowed absolute URL. - // Only call the slow filter_xss_bad_protocol if $path contains a ':'. - if (strpos($path, ':') !== FALSE && filter_xss_bad_protocol($path, FALSE) == check_plain($path)) { + // Only call the slow filter_xss_bad_protocol if $path contains a ':' before any / ? or #. + $colonpos = strpos($path, ':'); + if ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path)) { // Split off the fragment if (strpos($path, '#') !== FALSE) { list($path, $old_fragment) = explode('#', $path, 2); @@ -1326,6 +1327,49 @@ function drupal_urlencode($text) { } } + +/** + * Ensure the private key variable used to generate tokens is set. + * + * @return + * The private key + */ +function drupal_get_private_key() { + if (!($key = variable_get('drupal_private_key', 0))) { + $key = mt_rand(); + variable_set('drupal_private_key', $key); + } + return $key; +} + +/** + * Generate a token based on $value, the current user session and private key. + * + * @param $value + * An additional value to base the token on + */ +function drupal_get_token($value = '') { + $private_key = drupal_get_private_key(); + return md5(session_id() . $value . $private_key); +} + +/** + * Validate a token based on $value, the current user session and private key. + * + * @param $token + * The token to be validated. + * @param $value + * An additional value to base the token on. + * @param $skip_anonymous + * Set to true to skip token validation for anonymous users. + * @return + * True for a valid token, false for an invalid token. When $skip_anonymous is true, the return value will always be true for anonymous users. + */ +function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) { + global $user; + return (($skip_anonymous && $user->uid == 0) || ($token == md5(session_id() . $value . variable_get('drupal_private_key', '')))); +} + /** * Performs one or more XML-RPC request(s). * diff --git a/includes/form.inc b/includes/form.inc index f830db500df833104987077608af53c17343332c..72665a221d23ac29147afb58c7ae241102bda18c 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -70,19 +70,24 @@ function drupal_get_form($form_id, &$form, $callback = NULL) { $form_button_counter = array(0, 0); $form['#type'] = 'form'; + + // Add a token to any form displayed to authenticated users. + // This ensures that any submitted form was actually requested previously by the user to protect against + // cross site request forgeries. The default token can be bypassed by setting $form['#token'] to FALSE. + if (isset($form['#token'])) { - if (variable_get('cache', 0) && !$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET') { + if ($form['#token'] === FALSE || $user->uid == 0) { unset($form['#token']); } else { - // Make sure that a private key is set: - if (!variable_get('drupal_private_key', '')) { - variable_set('drupal_private_key', mt_rand()); - } - - $form['form_token'] = array('#type' => 'hidden', '#default_value' => md5(session_id() . $form['#token'] . variable_get('drupal_private_key', ''))); + $form['form_token'] = array('#type' => 'token', '#default_value' => drupal_get_token($form['#token'])); } } + else if ($user->uid) { + $form['#token'] = $form_id; + $form['form_token'] = array('#type' => 'token', '#default_value' => drupal_get_token($form['#token'])); + } + if (isset($form_id)) { $form['form_id'] = array('#type' => 'hidden', '#value' => $form_id, '#id' => str_replace('_', '-', "edit-$form_id")); } @@ -177,8 +182,9 @@ function drupal_validate_form($form_id, $form, $callback = NULL) { return; } + // Check whether the form token is valid. if (isset($form['#token'])) { - if ($form_values['form_token'] != md5(session_id() . $form['#token'] . variable_get('drupal_private_key', ''))) { + if (!drupal_valid_token($form_values['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.')); } @@ -376,6 +382,9 @@ function form_builder($form_id, $form) { $form['#value'] = $edit; } break; + case 'token': + $form['#value'] = (string)$edit; + break; default: if (isset($edit)) { $form['#value'] = $edit; @@ -1001,6 +1010,10 @@ function theme_hidden($element) { return '\n"; } +function theme_token($element) { + return theme('hidden', $element); +} + /** * Format a textfield. * diff --git a/includes/unicode.inc b/includes/unicode.inc index 6714437b221ffdde93e1efff7290c2498178bdd9..402cc2e7b6e73b780001f122c5573fc7c2bfd534 100644 --- a/includes/unicode.inc +++ b/includes/unicode.inc @@ -128,7 +128,7 @@ function drupal_xml_parser_create(&$data) { $data = ereg_replace('^(<\?xml[^>]+encoding)="([^"]+)"', '\\1="utf-8"', $out); } else { - watchdog('php', t("Could not convert XML encoding '%s' to UTF-8.", array('%s' => $encoding)), WATCHDOG_WARNING); + watchdog('php', t("Could not convert XML encoding '%s' to UTF-8.", array('%s' => theme('placeholder', $encoding))), WATCHDOG_WARNING); return 0; } } diff --git a/modules/block.module b/modules/block.module index 695b45ed9fb0476e8cb73c44aa3d60983046f155..c40676b37bde92e1da678bc58859ebb19863a47f 100644 --- a/modules/block.module +++ b/modules/block.module @@ -302,7 +302,7 @@ function theme_block_admin_display($form) { $last_region = ''; $last_status = 1; foreach (element_children($form) as $i) { - $block = $form[$i]; + $block = &$form[$i]; // Only take form elements that are blocks. if (is_array($block['info'])) { // Fetch values @@ -346,10 +346,7 @@ function theme_block_admin_display($form) { $output = theme('table', $header, $rows, array('id' => 'blocks')); $output .= form_render($form['submit']); - // Also render the form_id as there is no form_render($form) call (as form_render does not appear to handle the - // multi-dimensional block form array very well). - $output .= form_render($form['form_id']); - + $output .= form_render($form); return $output; } diff --git a/modules/blog.module b/modules/blog.module index 39088346914c57d36b3ce886ac0350d8b7d05a37..04bf21c2175eb5000878f74665e9a4bc401cc2ae 100644 --- a/modules/blog.module +++ b/modules/blog.module @@ -138,7 +138,7 @@ function blog_page_user($uid) { $account = user_load(array((is_numeric($uid) ? 'uid' : 'name') => $uid, 'status' => 1)); if ($account->uid) { - drupal_set_title($title = t("%name's blog", array('%name' => $account->name))); + drupal_set_title($title = t("%name's blog", array('%name' => check_plain($account->name)))); if (($account->uid == $user->uid) && user_access('edit own blog')) { $output = '
  • '. l(t('Post new blog entry.'), "node/add/blog") .'
  • '; diff --git a/modules/comment.module b/modules/comment.module index 2247f4cf6d94c80233373aaf4c191321b9bffddb..5963595000b5716cb2ef9959e455ad4228269417 100644 --- a/modules/comment.module +++ b/modules/comment.module @@ -872,28 +872,14 @@ function comment_render($node, $cid = 0) { */ function comment_delete($cid) { $comment = db_fetch_object(db_query('SELECT c.*, u.name AS registered_name, u.uid FROM {comments} c INNER JOIN {users} u ON u.uid = c.uid WHERE c.cid = %d', $cid)); - $comment->name = $comment->uid ? $comment->registered_name : $comment->name; - $output = ''; - // We'll only delete if the user has confirmed the - // deletion using the form in our else clause below. - if (is_object($comment) && is_numeric($comment->cid) && $_POST['edit']['confirm']) { - drupal_set_message(t('The comment and all its replies have been deleted.')); - - // Delete comment and its replies. - _comment_delete_thread($comment); - - _comment_update_node_statistics($comment->nid); - - // Clear the cache so an anonymous user sees that his comment was deleted. - cache_clear_all(); - - drupal_goto("node/$comment->nid"); - } - else if (is_object($comment) && is_numeric($comment->cid)) { + if (is_object($comment) && is_numeric($comment->cid)) { + $comment->name = $comment->uid ? $comment->registered_name : $comment->name; + $form = array(); + $form['comment'] = array('#type' => 'value', '#value' => $comment); $output = confirm_form('comment_confirm_delete', - array(), + $form, t('Are you sure you want to delete the comment %title?', array('%title' => theme('placeholder', $comment->subject))), 'node/'. $comment->nid, t('Any replies to this comment will be lost. This action cannot be undone.'), @@ -907,6 +893,20 @@ function comment_delete($cid) { return $output; } +function comment_confirm_delete_submit($form_id, $form_values) { + $comment = $form_values['comment']; + + // Delete comment and its replies. + _comment_delete_thread($comment); + + _comment_update_node_statistics($comment->nid); + + // Clear the cache so an anonymous user sees that his comment was deleted. + cache_clear_all(); + drupal_set_message(t('The comment and all its replies have been deleted.')); + return "node/$comment->nid"; +} + /** * Comment operations. We offer different update operations depending on * which comment administration page we're on. diff --git a/modules/contact.module b/modules/contact.module index 48f97137a1cda301fc52d37c4ee9c8c07c36bf4a..b18a8b556958b9af8acc2baf0da80b50e70a3750 100644 --- a/modules/contact.module +++ b/modules/contact.module @@ -305,10 +305,10 @@ function contact_mail_user() { drupal_access_denied(); } else if (!$account->contact && !$admin_access) { - $output = t('%name is not accepting e-mails.', array('%name' => $account->name)); + $output = t('%name is not accepting e-mails.', array('%name' => check_plain($account->name))); } else if (!$user->uid) { - $output = t('Please login or register to send %name a message.', array('%login' => url('user/login'), '%register' => url('user/register'), '%name' => $account->name)); + $output = t('Please login or register to send %name a message.', array('%login' => url('user/login'), '%register' => url('user/register'), '%name' => check_plain($account->name))); } else if (!valid_email_address($user->mail)) { $output = t('You need to provide a valid e-mail address to contact other users. Please update your user information and try again.', array('%url' => url("user/$user->uid/edit"))); @@ -317,16 +317,16 @@ function contact_mail_user() { $output = t('You cannot contact more than %number users per hour. Please try again later.', array('%number' => variable_get('contact_hourly_threshold', 3))); } else { - drupal_set_title($account->name); + drupal_set_title(check_plain($account->name)); $form['#token'] = $user->name . $user->mail; $form['from'] = array('#type' => 'item', '#title' => t('From'), - '#value' => $user->name .' <'. $user->mail .'>', + '#value' => check_plain($user->name) .' <'. $user->mail .'>', ); $form['to'] = array('#type' => 'item', '#title' => t('To'), - '#value' => $account->name, + '#value' => check_plain($account->name), ); $form['subject'] = array('#type' => 'textfield', '#title' => t('Subject'), diff --git a/modules/filter.module b/modules/filter.module index 101e72127230c0a101be5295ca4842f5805818b6..49f7fb76946b62c7187d65a3907dd6806ef4857a 100644 --- a/modules/filter.module +++ b/modules/filter.module @@ -1348,15 +1348,20 @@ function filter_xss_bad_protocol($string, $decode = TRUE) { if ($decode) { $string = decode_entities($string); } - // Remove soft hyphen - $string = str_replace(chr(194) . chr(173), '', $string); - // Strip protocols - + // Iteratively remove any invalid protocol found. do { $before = $string; $colonpos = strpos($string, ':'); if ($colonpos > 0) { + // We found a colon, possibly a protocol. Verify. $protocol = substr($string, 0, $colonpos); + // If a colon is preceded by a slash, question mark or hash, it cannot + // possibly be part of the URL scheme. This must be a relative URL, + // which inherits the (safe) protocol of the base document. + if (preg_match('![/?#]!', $protocol)) { + break; + } + // Check if this is a disallowed protocol if (!isset($allowed_protocols[$protocol])) { $string = substr($string, $colonpos + 1); } diff --git a/modules/forum.module b/modules/forum.module index 443c080712a79f01b3ffd769932aad295ca51da2..a725d6097b1b8af070a0d1474cba1df366140da2 100644 --- a/modules/forum.module +++ b/modules/forum.module @@ -873,7 +873,7 @@ function theme_forum_display($forums, $topics, $parents, $tid, $sortby, $forum_p } } - drupal_set_title($title); + drupal_set_title(check_plain($title)); $breadcrumb[] = array('path' => $_GET['q']); menu_set_location($breadcrumb); diff --git a/modules/locale.module b/modules/locale.module index a15ac2ef18c355089b548900db7c6633bd9d9f68..62086184cc83ae70075bbd0530583d9402d86560 100644 --- a/modules/locale.module +++ b/modules/locale.module @@ -411,6 +411,26 @@ function locale_admin_string_edit($lid) { * Delete a string. */ function locale_admin_string_delete($lid) { - include_once './includes/locale.inc'; - _locale_string_delete($lid); + $form = array(); + $string = db_result(db_query("SELECT source FROM {locales_source} WHERE lid = %d", $lid)); + + if ($string) { + $form['lid'] = array('#type' => 'value', '#value' => $lid); + $form['string'] = array('#type' => 'item', '#value'=> check_plain($string, 150, FALSE, TRUE)); + return confirm_form('locale_string_delete_confirm', + $form, + t('Are you sure you want to delete the following string?'), + 'admin/locale/string/search', + '', + t('Delete'), + t('Cancel')); + } + else { + drupal_not_found(); + } } + +function locale_string_delete_confirm_submit($form_id, $form_values) { + include_once './includes/locale.inc'; + _locale_string_delete($form_values['lid']); +} \ No newline at end of file diff --git a/modules/menu.module b/modules/menu.module index 28aac989848ba805fcdc73d0d4549c1d9eba9c1a..47177c6b89d6fd33dbe6a3588e550308d233616f 100644 --- a/modules/menu.module +++ b/modules/menu.module @@ -483,14 +483,22 @@ function menu_reset_item_form_submit($form_id, $form_values) { /** * Menu callback; hide a menu item. */ -function menu_disable_item($mid) { +function menu_disable_item($mid, $token = NULL) { + global $user; $item = menu_get_item($mid); - $type = $item['type']; + $form = array(); + $form['mid'] = array('#type' => 'value', '#value' => $mid); + $form['item'] = array('#type' => 'value', '#value' => $item); + return confirm_form('menu_disable_confirm', $form, t('Are you sure you want to disable the menu item %menu-item?', array('%menu-item' => theme('placeholder', $item['title']))), 'admin/menu', ' ', t('Disable'), t('Cancel')); +} + +function menu_disable_confirm_submit($form_id, $form_values) { + $type = $form_values['item']['type']; $type &= ~MENU_VISIBLE_IN_TREE; $type &= ~MENU_VISIBLE_IN_BREADCRUMB; $type |= MENU_MODIFIED_BY_ADMIN; - db_query('UPDATE {menu} SET type = %d WHERE mid = %d', $type, $mid); - drupal_set_message(t('The menu item has been disabled.')); + db_query('UPDATE {menu} SET type = %d WHERE mid = %d', $type, $form_values['mid']); + drupal_set_message(t('The menu item has been disabled.')); drupal_goto('admin/menu'); } diff --git a/modules/node.module b/modules/node.module index 93273cb0630d16b14fa52fe237abfd88510121a1..926be1365086e7566d8868c216a5195c45977b6a 100644 --- a/modules/node.module +++ b/modules/node.module @@ -43,7 +43,7 @@ function node_help($section) { return t('

    Enter a simple pattern to search for a post. This can include the wildcard character *.
    For example, a search for "br*" might return "bread bakers", "our daily bread" and "brenda".

    '); } - if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == 'revisions') { + if (arg(0) == 'node' && is_numeric(arg(1)) && arg(2) == 'revisions' && !arg(3)) { return t('The revisions let you track differences between multiple versions of a post.'); } @@ -881,6 +881,18 @@ function node_menu($may_cache) { 'access' => $revisions_access, 'weight' => 2, 'type' => MENU_LOCAL_TASK); + $items[] = array('path' => 'node/'. arg(1) .'/revisions/' . arg(3) . '/delete', + 'title' => t('revisions'), + 'callback' => 'node_revisions', + 'access' => $revisions_access, + 'weight' => 2, + 'type' => MENU_CALLBACK); + $items[] = array('path' => 'node/'. arg(1) .'/revisions/' . arg(3) . '/revert', + 'title' => t('revisions'), + 'callback' => 'node_revisions', + 'access' => $revisions_access, + 'weight' => 2, + 'type' => MENU_CALLBACK); } } else if (arg(0) == 'admin' && arg(1) == 'settings' && arg(2) == 'content-types' && is_string(arg(3))) { @@ -1305,16 +1317,12 @@ function node_revision_revert($nid, $revision) { $node = node_load($nid, $revision); if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) { if ($node->vid) { - $node->revision = 1; - $node->log = t('Copy of the revision from %date.', array('%date' => theme('placeholder', format_date($node->revision_timestamp)))); - if (module_exist('taxonomy')) { - $node->taxonomy = array_keys($node->taxonomy); - } - - node_save($node); - - drupal_set_message(t('%title has been reverted back to the revision from %revision-date', array('%revision-date' => theme('placeholder', format_date($node->revision_timestamp)), '%title' => theme('placeholder', check_plain($node->title))))); - watchdog('content', t('%type: reverted %title revision %revision.', array('%type' => theme('placeholder', t($node->type)), '%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $revision)))); + $form = array(); + $form['nid'] = array('#type' => 'value', '#value' => $node->nid); + $form['vid'] = array('#type' => 'value', '#value' => $node->vid); + return confirm_form('node_revision_revert_confirm', $form, + t('Are you sure you want to revert %title to the revision from %revision-date?', array('%title' => theme('placeholder', $node->title), '%revision-date' => theme('placeholder', format_date($node->revision_timestamp)))), + "node/$nid/revisions", ' ', t('Revert'), t('Cancel')); } else { drupal_set_message(t('You tried to revert to an invalid revision.'), 'error'); @@ -1324,6 +1332,22 @@ function node_revision_revert($nid, $revision) { drupal_access_denied(); } +function node_revision_revert_confirm_submit($form_id, $form_values) { + $nid = $form_values['nid']; + $revision = $form_values['vid']; + $node = node_load($nid, $revision); + $node->revision = 1; + $node->log = t('Copy of the revision from %date.', array('%date' => theme('placeholder', format_date($node->revision_timestamp)))); + if (module_exist('taxonomy')) { + $node->taxonomy = array_keys($node->taxonomy); + } + + node_save($node); + drupal_set_message(t('%title has been reverted back to the revision from %revision-date', array('%revision-date' => theme('placeholder', format_date($node->revision_timestamp)), '%title' => theme('placeholder', check_plain($node->title))))); + watchdog('content', t('%type: reverted %title revision %revision.', array('%type' => theme('placeholder', t($node->type)), '%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $revision)))); + return 'node/'. $nid .'/revisions'; +} + /** * Delete the revision with specified revision number. A "delete revision" nodeapi event is invoked when a * revision is deleted. @@ -1335,13 +1359,13 @@ function node_revision_delete($nid, $revision) { // Don't delete the current revision if ($revision != $node->vid) { $node = node_load($nid, $revision); - - db_query("DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d", $nid, $revision); - node_invoke_nodeapi($node, 'delete revision'); - drupal_set_message(t('Deleted %title revision %revision.', array('%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $revision)))); - watchdog('content', t('%type: deleted %title revision %revision.', array('%type' => theme('placeholder', t($node->type)), '%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $revision)))); + $form = array(); + $form['nid'] = array('#type' => 'value', '#value' => $nid); + $form['vid'] = array('#type' => 'value', '#value' => $revision); + return confirm_form('node_revision_delete_confirm', $form, + t('Are you sure you want to delete %title revision %revision?', array('%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $revision))), + "node/$nid/revisions", '', t('Delete'), t('Cancel')); } - else { drupal_set_message(t('Deletion failed. You tried to delete the current revision.')); } @@ -1353,10 +1377,22 @@ function node_revision_delete($nid, $revision) { } } } - drupal_access_denied(); } +function node_revision_delete_confirm_submit($form_id, $form_values) { + $node = node_load($form_values['nid'], $form_values['vid']); + db_query("DELETE FROM {node_revisions} WHERE nid = %d AND vid = %d", $node->nid, $node->vid); + node_invoke_nodeapi($node, 'delete revision'); + drupal_set_message(t('Deleted %title revision %revision.', array('%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $node->vid)))); + watchdog('content', t('%type: deleted %title revision %revision.', array('%type' => theme('placeholder', t($node->type)), '%title' => theme('placeholder', $node->title), '%revision' => theme('placeholder', $node->revision)))); + + if (db_result(db_query('SELECT COUNT(vid) FROM {node_revisions} WHERE nid = %d', $node->nid)) > 1) { + return "node/$node->nid/revisions"; + } + return "node/$node->nid"; +} + /** * Return a list of all the existing revision numbers. */ @@ -1948,10 +1984,10 @@ function node_revisions() { } break; case 'revert': - node_revision_revert(arg(1), arg(3)); + return node_revision_revert(arg(1), arg(3)); break; case 'delete': - node_revision_delete(arg(1), arg(3)); + return node_revision_delete(arg(1), arg(3)); break; } } diff --git a/modules/path.module b/modules/path.module index adb6c5f1dba8bdccf4b5f004722fe5feb4bb26cc..a83862c9cc2dfdb7af5ebbe3c3225ac276a73057 100644 --- a/modules/path.module +++ b/modules/path.module @@ -85,7 +85,7 @@ function path_admin() { function path_admin_edit($pid = 0) { if ($pid) { $alias = path_load($pid); - drupal_set_title($alias['dst']); + drupal_set_title(check_plain($alias['dst'])); $output = path_form(path_load($pid)); } else { diff --git a/modules/poll.module b/modules/poll.module index 431d8fa91979509a2ca28af9a80521bbd8982dc3..e6246d999f30d2023ed6d27dd384c1c182501e78 100644 --- a/modules/poll.module +++ b/modules/poll.module @@ -292,7 +292,8 @@ function poll_teaser($node) { * Generates the voting form for a poll. */ function poll_view_voting(&$node, $teaser, $page, $block) { - if ($_POST['op'] == t('Vote')) { + // Check for a valid form token to protect against cross site request forgeries. + if ($_POST['op'] == t('Vote') && drupal_valid_token($_POST['edit']['form_token'], 'poll_view_voting', TRUE)) { poll_vote($node); } diff --git a/modules/profile.module b/modules/profile.module index 123dc685ba3a63bd700187d6033e3134fe3e4c79..c8ebfe8fcc5393469354d22686f0b6093b1cfc25 100644 --- a/modules/profile.module +++ b/modules/profile.module @@ -137,7 +137,7 @@ function profile_block($op = 'list', $delta = 0, $edit = array()) { } if ($output) { - $block['subject'] = t('About %name', array('%name' => $account->name)); + $block['subject'] = t('About %name', array('%name' => check_plain($account->name))); $block['content'] = $output; return $block; } @@ -184,7 +184,7 @@ function profile_field_form($arg = NULL) { drupal_not_found(); return; } - drupal_set_title(t('edit %title', array('%title' => $edit['title']))); + drupal_set_title(t('edit %title', array('%title' => check_plain($edit['title'])))); $form['fid'] = array('#type' => 'value', '#value' => $fid, ); @@ -460,7 +460,7 @@ function profile_browse() { } $output .= ''; - drupal_set_title($title); + drupal_set_title(check_plain($title)); return $output; } else if ($name && !$field->fid) { diff --git a/modules/statistics.module b/modules/statistics.module index 1869b5c0ec4feac8819d233eb1b747b1988239d2..b4c7320a3ae35afc0a9de605a5384d0563426a98 100644 --- a/modules/statistics.module +++ b/modules/statistics.module @@ -216,7 +216,7 @@ function statistics_user_tracker() { l(t('details'), "admin/logs/access/$log->aid")); } - drupal_set_title($account->name); + drupal_set_title(check_plain($account->name)); $output = theme('table', $header, $rows); $output .= theme('pager', NULL, 30, 0); return $output; diff --git a/modules/system.module b/modules/system.module index c2732d264fafdc22e1b11483b5d2c0f4e5eb52cf..512784db7a8860d2b6284b5cb0a6b65ccea16854 100644 --- a/modules/system.module +++ b/modules/system.module @@ -6,7 +6,7 @@ * Configuration system that lets administrators modify the workings of the site. */ -define('VERSION', '4.7.4 dev'); +define('VERSION', '4.7.4'); /** * Implementation of hook_help(). @@ -84,6 +84,7 @@ function system_elements() { $type['value'] = array('#input' => TRUE); $type['markup'] = array('#prefix' => '', '#suffix' => ''); $type['fieldset'] = array('#collapsible' => FALSE, '#collapsed' => FALSE); + $type['token'] = array('#input'=> TRUE); return $type; } diff --git a/modules/tracker.module b/modules/tracker.module index aa076d2d4c588b78072f73c4b0c48f8943a93a60..40091bb62b9d6e77b25c2f0a45735931ea1e52c2 100644 --- a/modules/tracker.module +++ b/modules/tracker.module @@ -66,7 +66,7 @@ function tracker_menu($may_cache) { function tracker_track_user() { if ($account = user_load(array('uid' => arg(1)))) { if ($account->status || user_access('administer users')) { - drupal_set_title($account->name); + drupal_set_title(check_plain($account->name)); return tracker_page($account->uid); } else { diff --git a/modules/user.module b/modules/user.module index bc47f48ce1a4b5c7f05c8688ca467b09efaec448..ce884d8f3f1851e7447e15847fa22d0d6e124435 100644 --- a/modules/user.module +++ b/modules/user.module @@ -242,7 +242,8 @@ function user_validate_name($name) { '\x{205F}-\x{206F}'. // Various text hinting characters '\x{FEFF}'. // Byte order mark '\x{FF01}-\x{FF60}'. // Full-width latin - '\x{FFF9}-\x{FFFD}]/u', // Replacement characters + '\x{FFF9}-\x{FFFD}'. // Replacement characters + '\x{0}]/u', // NULL byte $name)) { return t('The username contains an illegal character.'); } @@ -581,7 +582,7 @@ function user_block($op = 'list', $delta = 0, $edit = array()) { case 1: if ($menu = theme('menu_tree')) { - $block['subject'] = $user->uid ? $user->name : t('Navigation'); + $block['subject'] = $user->uid ? check_plain($user->name) : t('Navigation'); $block['content'] = $menu; } return $block; @@ -1083,7 +1084,7 @@ function user_pass_submit($form_id, $form_values) { $mail_success = user_mail($account->mail, $subject, $body, $headers); if ($mail_success) { - watchdog('user', t('Password reset instructions mailed to %name at %email.', array('%name' => ''. $account->name .'', '%email' => ''. $account->mail .''))); + watchdog('user', t('Password reset instructions mailed to %name at %email.', array('%name' => theme('placeholder', $account->name), '%email' => theme('placeholder', $account->mail)))); drupal_set_message(t('Further instructions have been sent to your e-mail address.')); } else { @@ -1124,7 +1125,7 @@ function user_pass_reset($uid, $timestamp, $hashed_pass, $action = NULL) { else if ($account->uid && $timestamp > $account->login && $timestamp < $current && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login)) { // First stage is a confirmation form, then login if ($action == 'login') { - watchdog('user', t('User %name used one-time login link at time %timestamp.', array('%name' => "$account->name", '%timestamp' => $timestamp))); + watchdog('user', t('User %name used one-time login link at time %timestamp.', array('%name' => theme('placeholder', $account->name), '%timestamp' => theme('placeholder', $timestamp)))); // Update the user table noting user has logged in. // And this also makes this hashed password a one-time-only login. db_query("UPDATE {users} SET login = %d WHERE uid = %d", time(), $account->uid); @@ -1407,19 +1408,9 @@ function user_edit($category = 'account') { $edit = $_POST['op'] ? $_POST['edit'] : (array)$account; if (arg(2) == 'delete') { - if ($edit['confirm']) { - db_query('DELETE FROM {users} WHERE uid = %d', $account->uid); - db_query('DELETE FROM {sessions} WHERE uid = %d', $account->uid); - db_query('DELETE FROM {users_roles} WHERE uid = %d', $account->uid); - db_query('DELETE FROM {authmap} WHERE uid = %d', $account->uid); - watchdog('user', t('Deleted user: %name %email.', array('%name' => theme('placeholder', $account->name), '%email' => theme('placeholder', '<'. $account->mail .'>'))), WATCHDOG_NOTICE); - drupal_set_message(t('The account has been deleted.')); - module_invoke_all('user', 'delete', $edit, $account); - drupal_goto('admin/user'); - } - else { - return confirm_form('user_confirm_delete', array(), t('Are you sure you want to delete the account %name?', array('%name' => theme('placeholder', $account->name))), 'user/'. $account->uid, t('All submissions made by this user will be attributed to the anonymous account. This action cannot be undone.'), t('Delete'), t('Cancel')); - } + $form = array(); + $form['account'] = array('#type' => 'value', '#value' => $account); + return confirm_form('user_confirm_delete', $form, t('Are you sure you want to delete the account %name?', array('%name' => theme('placeholder', $account->name))), 'user/'. $account->uid, t('All submissions made by this user will be attributed to the anonymous account. This action cannot be undone.'), t('Delete'), t('Cancel')); } else if ($_POST['op'] == t('Delete')) { if ($_REQUEST['destination']) { @@ -1439,7 +1430,7 @@ function user_edit($category = 'account') { } $form['#attributes']['enctype'] = 'multipart/form-data'; - drupal_set_title($account->name); + drupal_set_title(check_plain($account->name)); return drupal_get_form('user_edit', $form); } @@ -1466,6 +1457,18 @@ function user_edit_submit($form_id, $form_values) { return 'user/'. $account->uid; } +function user_confirm_delete_submit($form_id, $form_values) { + $account = $form_values['account']; + db_query('DELETE FROM {users} WHERE uid = %d', $account->uid); + db_query('DELETE FROM {sessions} WHERE uid = %d', $account->uid); + db_query('DELETE FROM {users_roles} WHERE uid = %d', $account->uid); + db_query('DELETE FROM {authmap} WHERE uid = %d', $account->uid); + watchdog('user', t('Deleted user: %name %email.', array('%name' => theme('placeholder', $account->name), '%email' => theme('placeholder', '<'. $account->mail .'>'))), WATCHDOG_NOTICE); + drupal_set_message(t('The account has been deleted.')); + module_invoke_all('user', 'delete', $form_values, $account); + return 'admin/user'; +} + function user_view($uid = 0) { global $user; @@ -1485,7 +1488,7 @@ function user_view($uid = 0) { } } } - drupal_set_title($account->name); + drupal_set_title(check_plain($account->name)); return theme('user_profile', $account, $fields); } @@ -1596,12 +1599,15 @@ function user_admin_access_add($mask = NULL, $type = NULL) { if (!$edit['mask']) { form_set_error('mask', t('You must enter a mask.')); } - else { + else if (drupal_valid_token($_POST['edit']['form_token'], 'access_rule', TRUE)) { $aid = db_next_id('{access}_aid'); db_query("INSERT INTO {access} (aid, mask, type, status) VALUES ('%s', '%s', '%s', %d)", $aid, $edit['mask'], $edit['type'], $edit['status']); drupal_set_message(t('The access rule has been added.')); drupal_goto('admin/access/rules'); } + else { + form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.')); + } } else { $edit['mask'] = $mask; @@ -1646,11 +1652,14 @@ function user_admin_access_edit($aid = 0) { if (!$edit['mask']) { form_set_error('mask', t('You must enter a mask.')); } - else { + else if (drupal_valid_token($_POST['edit']['form_token'], 'access_rule', TRUE)) { db_query("UPDATE {access} SET mask = '%s', type = '%s', status = '%s' WHERE aid = %d", $edit['mask'], $edit['type'], $edit['status'], $aid); drupal_set_message(t('The access rule has been saved.')); drupal_goto('admin/access/rules'); } + else { + form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.')); + } } else { $edit = db_fetch_array(db_query('SELECT aid, type, status, mask FROM {access} WHERE aid = %d', $aid)); @@ -1865,32 +1874,49 @@ function user_admin_role() { $id = arg(4); if ($op == t('Save role')) { - if ($edit['name']) { - db_query("UPDATE {role} SET name = '%s' WHERE rid = %d", $edit['name'], $id); - drupal_set_message(t('The changes have been saved.')); - drupal_goto('admin/access/roles'); + // Check for a valid form token to protect against cross site request forgeries. + if (drupal_valid_token($edit['form_token'], 'user_admin_role', TRUE)) { + if ($edit['name']) { + db_query("UPDATE {role} SET name = '%s' WHERE rid = %d", $edit['name'], $id); + drupal_set_message(t('The changes have been saved.')); + drupal_goto('admin/access/roles'); + } + else { + form_set_error('name', t('You must specify a valid role name.')); + } } else { - form_set_error('name', t('You must specify a valid role name.')); + form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.')); } } else if ($op == t('Delete role')) { - db_query('DELETE FROM {role} WHERE rid = %d', $id); - db_query('DELETE FROM {permission} WHERE rid = %d', $id); - // Update the users who have this role set: - db_query('DELETE FROM {users_roles} WHERE rid = %d', $id); - - drupal_set_message(t('The role has been deleted.')); - drupal_goto('admin/access/roles'); + // Check for a valid form token to protect against cross site request forgeries. + if (drupal_valid_token($edit['form_token'], 'user_admin_role', TRUE)) { + db_query('DELETE FROM {role} WHERE rid = %d', $id); + db_query('DELETE FROM {permission} WHERE rid = %d', $id); + // Update the users who have this role set: + db_query('DELETE FROM {users_roles} WHERE rid = %d', $id); + drupal_set_message(t('The role has been deleted.')); + drupal_goto('admin/access/roles'); + } + else { + form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.')); + } } else if ($op == t('Add role')) { - if ($edit['name']) { - db_query("INSERT INTO {role} (name) VALUES ('%s')", $edit['name']); - drupal_set_message(t('The role has been added.')); - drupal_goto('admin/access/roles'); + // Check for a valid form token to protect against cross site request forgeries. + if (drupal_valid_token($edit['form_token'], 'user_admin_new_role', TRUE)) { + if ($edit['name']) { + db_query("INSERT INTO {role} (name) VALUES ('%s')", $edit['name']); + drupal_set_message(t('The role has been added.')); + drupal_goto('admin/access/roles'); + } + else { + form_set_error('name', t('You must specify a valid role name.')); + } } else { - form_set_error('name', t('You must specify a valid role name.')); + form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.')); } } if ($id) { @@ -1921,7 +1947,10 @@ function theme_user_admin_new_role($form) { } $rows[] = array(form_render($form['name']), form_render($form['submit'])); - return theme('table', $header, $rows); + $output = theme('table', $header, $rows); + $output .= form_render($form); + + return $output; } function user_admin_account() { diff --git a/update.php b/update.php index ff607cec193f98183ae6c46bcfb71b4e00e4f99d..ce028fd7289f656a23f7b579e6d4c1b71ad09514 100644 --- a/update.php +++ b/update.php @@ -681,7 +681,14 @@ function update_convert_table_utf8($table) { $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : ''; switch ($op) { case 'Update': - $output = update_update_page(); + // Check for a valid form token to protect against cross site request forgeries. + if (drupal_valid_token($_REQUEST['edit']['form_token'], 'update_script_selection_form', TRUE)) { + $output = update_update_page(); + } + else { + form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.')); + $output = update_selection_page(); + } break; case 'finished':