session_regenerate_id
(PHP 4 >= 4.3.2, PHP 5, PHP 7, PHP 8)
session_regenerate_id — 現在のセッションIDを新しく生成したものと置き換える
説明
session_regenerate_id() は現在のセッションIDを 新しいものと置き換えます。その際、現在のセッション情報は維持されます。
session.use_trans_sid が有効な場合は、 session_regenerate_id() を呼んでから出力を始めなければいけません。 そうしないと、古いセッション ID が使われます。
現在の session_regenerate_id は、モバイルネットワークや WiFi ネットワークなどの 不安定なネットワークをうまく扱えません。 そのため、 session_regenerate_id を 呼ぶと、セッションが消失する恐れがあります。
すぐに古いセッションデータを破棄すべきではありませんが、 古いセッションID のタイムスタンプと、 アクセス制御は破棄すべきでしょう。 さもないと、ページへの同時アクセスによって、 状態の不整合が発生したり、セッションが失われたり、 クライアント(ブラウザ) 側のレースコンディションが発生したり、 不必要なセッションIDをたくさん作らなければならなくなるかもしれません。 セッションデータをすぐに破棄してしまうと、 セッションハイジャックの検出だけでなく、 防止もできなくしてしまいます。
パラメータ
delete_old_session
-
関連付けられた古いセッションを削除するかどうか。 削除による競合を防いだり、セッションハイジャックを 検出/防ぐ 必要がある場合には、古いセッションを削除すべきではありません。
例
例1 session_regenerate_id() の使用例
<?php
// 注意: このコードは完全に動作するわけではありません。一例です!
session_start();
// 破棄されたタイムスタンプをチェック
if (isset($_SESSION['destroyed'])
&& $_SESSION['destroyed'] < time() - 300) {
// 通常は起こるべきではない。攻撃や不安定なネットワークによる可能性がある
// このユーザーのセッションから、全ての認証ステータスを削除
remove_all_authentication_flag_from_active_sessions($_SESSION['userid']);
throw(new DestroyedSessionAccessException);
}
$old_sessionid = session_id();
// 破棄されたタイムスタンプを設定
$_SESSION['destroyed'] = time(); // session_regenerate_id() は古いセッションデータを保存します。
// 単に session_regenerate_id() を呼ぶだけでは、セッションの消失などに繋がる可能性があります。
// 次の例を見てください。
session_regenerate_id();
// 新しいセッションには、破棄されたタイムスタンプは不要
unset($_SESSION['destroyed']);
$new_sessionid = session_id();
echo "古いセッション: $old_sessionid<br />";
echo "新しいセッション: $new_sessionid<br />";
print_r($_SESSION);
?>
現在のセッションモジュールは、不安定なネットワークをうまく扱えません。 session_regenerate_id によるセッションの消失を避けるために、 セッションIDを管理すべきです。
例2 session_regenerate_id() による、セッションの消失を避ける
<?php
// 注意: このコードは完全に動作するわけではありません。一例です!
// my_session_start() and my_session_regenerate_id() によって、
// 不安定なネットワークによるセッションの消失を避けています。
// それに加えて、このコードは攻撃者から、盗まれたセッションを利用した
// 攻撃を防ぐ可能性もあります。
function my_session_start() {
session_start();
if (isset($_SESSION['destroyed'])) {
if ($_SESSION['destroyed'] < time()-300) {
// 通常は起こるべきではない。攻撃や不安定なネットワークによる可能性がある
// このユーザーのセッションから、全ての認証ステータスを削除
remove_all_authentication_flag_from_active_sessions($_SESSION['userid']);
throw(new DestroyedSessionAccessException);
}
if (isset($_SESSION['new_session_id'])) {
// 完全に expire してはいない。
// Cookie が不安定なネットワークによって失われた可能性がある。
// 適切なセッションIDのクッキーを設定するためにリトライする。
// 注意: 認証フラグを削除したい場合は、セッションIDを再度設定しようとしてはいけない。
session_commit();
session_id($_SESSION['new_session_id']);
// 新しいセッションIDが存在しているはず
session_start();
return;
}
}
}
function my_session_regenerate_id() {
// 不安定なネットワークのために、セッションID が設定されなかったときは、
// 新しいセッションID が、適切なセッションIDに設定されることが必須。
$new_session_id = session_create_id();
$_SESSION['new_session_id'] = $new_session_id;
// 破棄された時のタイムスタンプを設定
$_SESSION['destroyed'] = time();
// 現在のセッションを書き込んで閉じる
session_commit();
// 新しいセッションを新しいセッションIDで開始
session_id($new_session_id);
ini_set('session.use_strict_mode', 0);
session_start();
ini_set('session.use_strict_mode', 1);
// 新しいセッションには、以下の情報は不要
unset($_SESSION['destroyed']);
unset($_SESSION['new_session_id']);
}
?>
参考
- session_id() - 現在のセッション ID を取得または設定する
- session_create_id() - 新しいセッションIDを作成する
- session_start() - 新しいセッションを開始、あるいは既存のセッションを再開する
- session_destroy() - セッションに登録されたデータを全て破棄する
- session_reset() - session 配列を元の値で再初期化する
- session_name() - 現在のセッション名を取得または設定する
User Contributed Notes 22 notes
I wrote the current top voted comment on this and wanted to add something. The existing code from my previous comment generates it's nonces in an insecure way-
<?php
$_SESSION['nonce'] = md5(microtime(true));
?>
Since "microtime" is predictable it makes brute forcing the nonce much easier. A better option would be something that utilizes randomness, such as-
<?php
bin2hex(openssl_random_pseudo_bytes(32))
?>
I wrote the following code for a project I'm working on- it attempts to resolve the regenerate issue, as well as deal with a couple of other session related things.
I tried to make it a little more generic and usable (for instance, in the full version it throws different types of exceptions for the different types of session issues), so hopefully someone might find it useful.
<?php
function regenerateSession($reload = false)
{
// This token is used by forms to prevent cross site forgery attempts
if(!isset($_SESSION['nonce']) || $reload)
$_SESSION['nonce'] = md5(microtime(true));
if(!isset($_SESSION['IPaddress']) || $reload)
$_SESSION['IPaddress'] = $_SERVER['REMOTE_ADDR'];
if(!isset($_SESSION['userAgent']) || $reload)
$_SESSION['userAgent'] = $_SERVER['HTTP_USER_AGENT'];
//$_SESSION['user_id'] = $this->user->getId();
// Set current session to expire in 1 minute
$_SESSION['OBSOLETE'] = true;
$_SESSION['EXPIRES'] = time() + 60;
// Create new session without destroying the old one
session_regenerate_id(false);
// Grab current session ID and close both sessions to allow other scripts to use them
$newSession = session_id();
session_write_close();
// Set session ID to the new one, and start it back up again
session_id($newSession);
session_start();
// Don't want this one to expire
unset($_SESSION['OBSOLETE']);
unset($_SESSION['EXPIRES']);
}
function checkSession()
{
try{
if($_SESSION['OBSOLETE'] && ($_SESSION['EXPIRES'] < time()))
throw new Exception('Attempt to use expired session.');
if(!is_numeric($_SESSION['user_id']))
throw new Exception('No session started.');
if($_SESSION['IPaddress'] != $_SERVER['REMOTE_ADDR'])
throw new Exception('IP Address mixmatch (possible session hijacking attempt).');
if($_SESSION['userAgent'] != $_SERVER['HTTP_USER_AGENT'])
throw new Exception('Useragent mixmatch (possible session hijacking attempt).');
if(!$this->loadUser($_SESSION['user_id']))
throw new Exception('Attempted to log in user that does not exist with ID: ' . $_SESSION['user_id']);
if(!$_SESSION['OBSOLETE'] && mt_rand(1, 100) == 1)
{
$this->regenerateSession();
}
return true;
}catch(Exception $e){
return false;
}
}
?>
In PHP 5.6 (and probably older versions), session_regenerate_id(true) do not trigger a read() call to the session handler for the new session id.
In PHP 7, read() is triggered during session_regenerate_id(true). Nice to know when working with custom session handlers.
If you are trying to maintain 2 active sessions don't use session_regenerate_id(). Especially if the first session is closed and it's time to open the second. Because the session id is cached you also have to explicitly set it the second time.
<?php
session_name('PHPSESSID'); // redundant - here for clarity
session_start();
// ...do stuff
session_write_close();
// now switch to session 2...
session_name('PHPSESSID_2');
if (isset($_COOKIE['phpsessid_2']))
session_id($_COOKIE['phpsessid_2']); // not doing this will simply reopen the first session again
else
session_id(sha1(mt_rand()); // dont use session_regenerate_id() here. Not creating a new id will create two cookies with same session id and same session variables
session_start();
// ... do stuff with session 2
session_write_close();
?>
In a previous note, php at 5mm de describes how to prevent session hijacking by
ensuring that the session id provided matches the HTTP_USER_AGENT and REMOTE_ADDR fields that were present when the session id was first issued. It should be noted that HTTP_USER_AGENT is supplied by the client, and so can be easily modified by a malicious user. Also, the client IP addresses can be spoofed, although that's a bit more difficult. Care should be taken when relying on the session for authentication.
`session_regenerate_id` sends a new cookie but doesn't overwrite the value stored in `$_COOKIE`. After calling `session_destroy`, the open session ID is discarded, so simply restarting the session with `session_start` (as done in Ben Johnson's code) will re-open the original, though now empty, session for the current request (subsequent requests will use the new session ID). Instead of `session_destroy`+`session_start`, use the `$delete_old_session` parameter to `session_regenerate_id` to delete the previous session data.
<?php
session_start();
/* Create a new session, deleting the previous session data. */
session_regenerate_id(TRUE);
/* erase data carried over from previous session */
$_SESSION=array();
?>
To start a new session and leave the old untouched, simply leave out the argument to `session_regenerate_id`.
Also note that REMOTE_ADDR may change on every request if the user comes through a proxy farm. Most AOL-users do.
To add to php at 5mm de's comments:
If the session is held over https, it's even better to save the client's cert or ssl session id instead of the hostname or ip, as it's proxy-transparent and more secure.
Document example is wrong with usage of "session.use_strict_mode" according to RFC (says: "warning error for session_id() when use_strice_mode=1" on https://wiki.php.net/rfc/strict_sessions).
So, this directive affects "session_id()" not "session_start()". So usage must be like this;
<?php
// first set ini
ini_set('session.use_strict_mode', '0');
// and
session_id($sid);
// then
// maybe run this: ini_restore('session.use_strict_mode');
// then go on...
?>
Refs (ctrl+f & use_strict_mode);
https://wiki.php.net/rfc/strict_sessions
https://wiki.php.net/rfc/session-create-id
http://php.net/manual/en/function.session-id.php#119997
I had problems with a proxy changing a visitors session_id-cookie, so he'd get a LOT of errors when visiting my site.
I handled the bogus session-id's like this. (Note: It only works in versions > 4.3.2.)
<?php
// Start a session and suppress error-messages.
@session_start();
// Catch bogus session-id's.
if (!preg_match("/^[0-9a-z]*$/i", session_id())) {
// Output a warning about the messed up session-id.
$error->handleError("WARN", "Your session id is messed up, you might not be able to use some features on this site.");
// Generate a fresh session-id.
session_regenerate_id();
}
// Site contents.
?>
Hope someone can use it.
Session_destroy() does not only destroy the data associated with the current session_id (i.e. the file if you use the default session save handler), but also the session itself: if you call session_destroy() and then session_regenerate_id(), it will return false, and session_id() won't return anything. In order to manipulate a session after destroying it, you need to restart it.
So in fact, the code mentionned by chris won't work. If you want to destroy the file associated with the old session_id, try the following:
<?php
session_start();
$old_sessid = session_id();
session_regenerate_id();
$new_sessid = session_id();
session_id($old_sessid);
session_destroy();
//If you don't copy the $_SESSION array, you won't be able to use the data associated with the old session id.
$old_session = $_SESSION;
session_id($new_sessid);
session_start();
$_SESSION = $old_session;
//...
?>
Note: this technique will send 3 Set-Cookie headers (one on each session_start() and one on session_regenerate_id()). I don't think this is a problem, but if it appears to be one, you could either leave it alone and wait for the garbage collector to catch the file associated with the old session, or try to delete the file with unlink().
The code example provided of my_session_regenerate_id() DOES NOT work and DESTROYS all session variables. Plus the second ini_set gives an error.
The code for regenerating (only that part, the rest seems fine) should simply be this:
<?php
function my_session_regenerate_id() {
$new_session_id = session_create_id();
// backup session variables
$keepSession = $_SESSION ;
// add info for users with bad connection not receiving the new session id
$_SESSION['new_session_id'] = $new_session_id;
// Set destroy timestamp
$_SESSION['destroyed'] = time();
// Write and close current session;
session_commit() ;
// Start session with new session ID
ini_set('session.use_strict_mode', 0);
session_id($new_session_id);
session_start();
$_SESSION = $keepSession ;
}
?>
This can be a very dangerous function if you're not careful about how you handle things, because even though it generates a whole new set of session data, it keeps the old data "open" until the script terminates, locking out any other scripts trying to run concurrently with the old session id.
Recently I came across a situation where I wanted to explicitly pass in a session ID, copy the data from that session into a *new* session, and then continue operating under that new session, thereby allowing other scripts to use the old one concurrently. But I quickly found that these "other scripts" would not execute until the first script finished--even though it had already started a new session--because it kept the old session open.
So if you're trying to copy over session data to a new session to free up the old session for continued, concurrent use, here's some code to ensure nobody's feet get stepped on:
<?php
// get session id of an existing session
$sid = $_GET['sid'];
// start the old session to retrieve $_SESSION data
session_id($sid);
session_start();
// start a new session; this copies the $_SESSION data over
session_regenerate_id();
// hang on to the new session id
$sid = session_id();
// close the old and new sessions
session_write_close();
// re-open the new session
session_id($sid);
session_start();
/* main code here */
?>
This could probably be encapsulated into a function with one parameter as well to save space if it was a repeated thing.
If you are storing your session data in a database you have to manually update the session_id in the database. The session_set_save_handler() will not do it for you.
function UpdateSessID() {
$old_sess_id = session_id();
session_regenerate_id(false);
$new_sess_id = session_id();
$query = "UPDATE `session_table` SET `session_id` = '$new_sess_id' WHERE session_id = '$old_sess_id'";
mysql_query($query);
}
Be sure to set session_regenerate_id() to FALSE since it's not really necessary to delete the whole record from MySQL and add it again. That's unnecessary overhead. Only changing the id matters.
There could be a potential problem with elger at NOSPAM dot yellowbee dot nl's a few posts below. In the code, was used the REQUEST_URI server variable, which, in some cases might already contain the query string. Therefore, always apending '?whatever=foo' would occasionally cause the script to malfunction. I suggest using PHP_SELF, which will not contain the query string after the file.
Note that in current PHP 7.2 nightly builds example #2 above will not work as shown. You will get following error upon trying to turn strict mode back on after session_start():
"ini_set(): A session is active. You cannot change the session module's ini settings at this time"
I suppose this means that for any session where you perform session ID regeneration or session ID forwarding (from a session for which a recent session ID regeneration was performed to the new session ID). That you will just have to live with strict mode being disabled for the remainder of that active session.
I don't know that this is really a security concern so long as you are following a single-session per request design (i.e. you are not working with multiple concurrent sessions).
licp - no, session_regenerate_id() does not destroy any saved session data.
elger, I prefer the following order
[code]
// populate $_SESSION with any previously saved session data for the current session_id
session_start();
...
// delete any saved data associated with current session_id, $_SESSION is not changed
session_destroy();
// change session_id, $_SESSION not altered
session_regenerate_id();
...
// save any $_SESSION data under the current session_id
session_close();
[/code]
<?php
function my_session_regenerate_id() {
...
session_start();
...
unset($_SESSION['destroyed']);
unset($_SESSION['new_session_id']);
}
?>
Within my_session_regenerate_id(), unset($_SESSION['destroyed']) and
unset($_SESSION['new_session_id']) are useless because session_start() will open an empty session. They would be needed if built-in session_regenerate_id() was called.