diff --git a/core/includes/session.inc b/core/includes/session.inc index df70f0e..5e97921 100644 --- a/core/includes/session.inc +++ b/core/includes/session.inc @@ -85,41 +85,70 @@ function _drupal_session_read($sid) { } // Otherwise, if the session is still active, we have a record of the - // client's session in the database. If it's HTTPS then we are either have - // a HTTPS session or we are about to log in so we check the sessions table - // for an anonymous session with the non-HTTPS-only cookie. + // client's session in the database. if ($is_https) { + // For HTTPS connections, attempt to get an HTTPS session. $user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.ssid = :ssid", array(':ssid' => $sid))->fetchObject(); - if (!$user) { - if (isset($_COOKIE[$insecure_session_name])) { - $user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid AND s.uid = 0", array( - ':sid' => $_COOKIE[$insecure_session_name])) - ->fetchObject(); + + // If mixed mode and there's no HTTPS session, attempt to get an HTTP session. + if (variable_get('https', FALSE) && !$user) { + $user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid", array(':sid' => $sid))->fetchObject(); + + // If HTTP session was found but it has an associated HTTPS session, reset + // session as it's likely a hijack attempt. + if ($user && $user->ssid != '') { + unset($user); + session_id(drupal_hash_base64(uniqid(mt_rand(), TRUE))); + $sid = session_id(); } } } else { + // Otherwise, for HTTP connections, attempt to get an HTTP session. $user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid", array(':sid' => $sid))->fetchObject(); } - // We found the client's session record and they are an authenticated, - // active user. - if ($user && $user->uid > 0 && $user->status == 1) { - // This is done to unserialize the data member of $user. - $user->data = unserialize($user->data); + // If a client's session record was found. + if ($user) { + // Set sid and ssid based on the database data. + $sid = $user->sid; + $ssid = $user->ssid; + + // If mixed mode and using http when there is an https cookie, redirect to + // https. + if (variable_get('https', FALSE) && !$is_https && $ssid != '') { + require_once DRUPAL_ROOT . '/core/includes/common.inc'; + require_once DRUPAL_ROOT . '/core/includes/path.inc'; + + $url = request_uri(); + $options = array('https' => TRUE); + if (strpos($url, '?') !== FALSE) { + $url = explode('?', $url, 2); + parse_str($url[1], $options['query']); + $url = $url[0]; + } + $url = trim($url, '/'); + drupal_goto($url, $options, 303); + } - // Add roles element to $user. - $user->roles = array(); - $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user'; - $user->roles += db_query("SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = :uid", array(':uid' => $user->uid))->fetchAllKeyed(0, 1); - } - elseif ($user) { - // The user is anonymous or blocked. Only preserve two fields from the - // {sessions} table. - $account = drupal_anonymous_user(); - $account->session = $user->session; - $account->timestamp = $user->timestamp; - $user = $account; + // If they are an authenticated, active user. + if ($user->uid > 0 && $user->status == 1) { + // This is done to unserialize the data member of $user. + $user->data = unserialize($user->data); + + // Add roles element to $user. + $user->roles = array(); + $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user'; + $user->roles += db_query("SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = :uid", array(':uid' => $user->uid))->fetchAllKeyed(0, 1); + } + else { + // The user is anonymous or blocked. Only preserve two fields from the + // {sessions} table. + $account = drupal_anonymous_user(); + $account->session = $user->session; + $account->timestamp = $user->timestamp; + $user = $account; + } } else { // The session has expired. @@ -131,6 +160,7 @@ function _drupal_session_read($sid) { $last_read = &drupal_static('drupal_session_last_read'); $last_read = array( 'sid' => $sid, + 'ssid' => $ssid, 'value' => $user->session, ); @@ -168,11 +198,11 @@ function _drupal_session_write($sid, $value) { // Check whether $_SESSION has been changed in this request. $last_read = &drupal_static('drupal_session_last_read'); - $is_changed = !isset($last_read) || $last_read['sid'] != $sid || $last_read['value'] !== $value; + $is_changed = !isset($last_read) || ($last_read['sid'] != $sid && $last_read['ssid'] != $sid) || $last_read['value'] !== $value; // For performance reasons, do not update the sessions table, unless // $_SESSION has changed or more than 180 has passed since the last update. - if ($is_changed || REQUEST_TIME - $user->timestamp > variable_get('session_write_interval', 180)) { + if ($is_changed || !isset($user->timestamp) || REQUEST_TIME - $user->timestamp > variable_get('session_write_interval', 180)) { // Either ssid or sid or both will be added from $key below. $fields = array( 'uid' => $user->uid, @@ -189,14 +219,38 @@ function _drupal_session_write($sid, $value) { // On HTTPS connections, use the session ID as both 'sid' and 'ssid'. if ($is_https) { $key['ssid'] = $sid; - // The "secure pages" setting allows a site to simultaneously use both - // secure and insecure session cookies. If enabled and both cookies are - // presented then use both keys. - if (variable_get('https', FALSE)) { + } + + // The "https" session mixed-mode setting allows a site to simultaneously + // use both secure and insecure session cookies. + if (variable_get('https', FALSE)) { + // For HTTPS connections, we need to ensure we're using both keys. + if ($is_https) { + // Get the session names to use based on the HTTPS session. + $session_name = session_name(); $insecure_session_name = substr(session_name(), 1); - if (isset($_COOKIE[$insecure_session_name])) { - $key['sid'] = $_COOKIE[$insecure_session_name]; + // If using the HTTP session, instead we need to get the session + // names based on it + if (strpos(session_name(), 'SSESS') !== 0) { + $session_name = 'S' . session_name(); + $insecure_session_name = session_name(); + } + // If the data has changed (rather than just a timely update) and + // either the HTTP or HTTPS cookies don't exist, regenerate the + // session. + if ($is_changed && (!isset($_COOKIE[$insecure_session_name]) || !isset($_COOKIE[$session_name]))) { + // Ensure session is regenerated as though one already exists. + // Otherwise, if the session doesn't already exist, an HTTP cookie + // will not be created. + drupal_session_started(TRUE); + drupal_session_regenerate(); } + $key = array('sid' => $_COOKIE[$insecure_session_name], 'ssid' => $_COOKIE[$session_name]); + } + // Otherwise, for HTTP connections, only use the sid key as it doesn't + // matter what the ssid is. + else { + unset($key['ssid']); } } @@ -240,15 +294,16 @@ function drupal_session_initialize() { // We use !empty() in the following check to ensure that blank session IDs // are not valid. - if (!empty($_COOKIE[session_name()]) || ($is_https && variable_get('https', FALSE) && !empty($_COOKIE[substr(session_name(), 1)]))) { + if (!empty($_COOKIE[session_name()])) { // If a session cookie exists, initialize the session. Otherwise the // session is only started on demand in drupal_session_commit(), making // anonymous users not use a session cookie unless something is stored in // $_SESSION. This allows HTTP proxies to cache anonymous pageviews. drupal_session_start(); - if (!empty($user->uid) || !empty($_SESSION)) { - drupal_page_is_cacheable(FALSE); - } + } + elseif($is_https && variable_get('https', FALSE) && !empty($_COOKIE[substr(session_name(), 1)])) { + session_name(substr(session_name(), 1)); + drupal_session_start(); } else { // Set a session identifier for this request. This is necessary because @@ -270,6 +325,8 @@ function drupal_session_initialize() { * @ingroup php_wrappers */ function drupal_session_start() { + global $user; + // Command line clients do not support cookies nor sessions. if (!drupal_session_started() && !drupal_is_cli()) { // Save current session data before starting it, as PHP will destroy it. @@ -283,6 +340,10 @@ function drupal_session_start() { $_SESSION += $session_data; } } + + if (!empty($user->uid) || !empty($_SESSION)) { + drupal_page_is_cacheable(FALSE); + } } /** @@ -334,52 +395,62 @@ function drupal_session_started($set = NULL) { */ function drupal_session_regenerate() { global $user, $is_https; - if ($is_https && variable_get('https', FALSE)) { - $insecure_session_name = substr(session_name(), 1); - if (isset($_COOKIE[$insecure_session_name])) { - $old_insecure_session_id = $_COOKIE[$insecure_session_name]; - } - $params = session_get_cookie_params(); - $session_id = drupal_hash_base64(uniqid(mt_rand(), TRUE) . drupal_random_bytes(55)); - // If a session cookie lifetime is set, the session will expire - // $params['lifetime'] seconds from the current request. If it is not set, - // it will expire when the browser is closed. - $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0; - setcookie($insecure_session_name, $session_id, $expire, $params['path'], $params['domain'], FALSE, $params['httponly']); - $_COOKIE[$insecure_session_name] = $session_id; - } if (drupal_session_started()) { - $old_session_id = session_id(); - } - session_id(drupal_hash_base64(uniqid(mt_rand(), TRUE) . drupal_random_bytes(55))); + $new_id = drupal_hash_base64(uniqid(mt_rand(), TRUE) . drupal_random_bytes(55)); - if (isset($old_session_id)) { - $params = session_get_cookie_params(); - $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0; - setcookie(session_name(), session_id(), $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']); - $fields = array('sid' => session_id()); + $last_read = &drupal_static('drupal_session_last_read'); + + $reset_cookies = array(session_name()); + + $id_field = 'sid'; + $id_value = $last_read['sid']; + $fields = array('sid' => $new_id); if ($is_https) { - $fields['ssid'] = session_id(); - // If the "secure pages" setting is enabled, use the newly-created - // insecure session identifier as the regenerated sid. - if (variable_get('https', FALSE)) { - $fields['sid'] = $session_id; + $id_field = 'ssid'; + $id_value = $last_read['ssid']; + $fields['ssid'] = $new_id; + } + + if (variable_get('https', FALSE)) { + $new_id2 = drupal_hash_base64(uniqid(mt_rand(), TRUE) . drupal_random_bytes(55)); + if ($is_https) { + $fields['sid'] = $new_id2; + if (strpos(session_name(), 'SSESS') === 0) { + $reset_cookies[] = substr(session_name(), 1); + } + else { + $id_field = 'sid'; + $id_value = $last_read['sid']; + $reset_cookies[] = 'S' . session_name(); + } + } + elseif (isset($last_read['ssid'])) { + $fields['ssid'] = $new_id2; + $reset_cookies[] = 'S' . session_name(); } } + db_update('sessions') ->fields($fields) - ->condition($is_https ? 'ssid' : 'sid', $old_session_id) - ->execute(); - } - elseif (isset($old_insecure_session_id)) { - // If logging in to the secure site, and there was no active session on the - // secure site but a session was active on the insecure site, update the - // insecure session with the new session identifiers. - db_update('sessions') - ->fields(array('sid' => $session_id, 'ssid' => session_id())) - ->condition('sid', $old_insecure_session_id) + ->condition($id_field, $id_value) ->execute(); + + session_id($new_id); + + $params = session_get_cookie_params(); + $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0; + foreach ($reset_cookies as $name) { + $value = $fields['sid']; + $secure = FALSE; + if (strpos($name, 'SSESS') === 0) { + $value = $fields['ssid']; + $secure = TRUE; + } + + setcookie($name, $value, $expire, $params['path'], $params['domain'], $secure, $params['httponly']); + $_COOKIE[$name] = $value; + } } else { // Start the session when it doesn't exist yet. @@ -389,6 +460,7 @@ function drupal_session_regenerate() { drupal_session_start(); $user = $account; } + date_default_timezone_set(drupal_get_user_timezone()); } diff --git a/core/modules/user/user.pages.inc b/core/modules/user/user.pages.inc index 8239c53..91ed09b 100644 --- a/core/modules/user/user.pages.inc +++ b/core/modules/user/user.pages.inc @@ -171,8 +171,8 @@ function user_logout() { module_invoke_all('user_logout', $user); - // Destroy the current session, and reset $user to the anonymous user. - session_destroy(); + // Reset $user to the anonymous user. + $user = drupal_anonymous_user(); drupal_goto(); }