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':