PHP Conference Japan 2024

session_regenerate_id

(PHP 4 >= 4.3.2, PHP 5, PHP 7, PHP 8)

session_regenerate_id 使用新產生的 ID 更新目前的 session ID

描述

session_regenerate_id(bool $delete_old_session = false): bool

session_regenerate_id() 將會用新的 session ID 取代目前的 session ID,並保留目前的 session 資訊。

session.use_trans_sid 啟用時,輸出必須在 session_regenerate_id() 呼叫後開始。否則,將會使用舊的 session ID。

警告

目前,session_regenerate_id 無法很好地處理不穩定的網路,例如行動網路和 WiFi 網路。因此,您可能會因呼叫 session_regenerate_id 而遺失 session。

您不應立即銷毀舊的 session 資料,而應該使用銷毀時間戳記並控制對舊 session ID 的存取。否則,同時存取頁面可能會導致狀態不一致,或您可能會遺失 session,或者可能導致客戶端(瀏覽器)端的競爭條件,並可能不必要地建立許多 session ID。立即刪除 session 資料也會停用 session 劫持攻擊偵測和預防功能。

參數

delete_old_session

是否要刪除舊的相關聯 session 檔案。如果您需要避免因刪除而引起的競爭或偵測/避免 session 劫持攻擊,則不應刪除舊的 session。

回傳值

成功時回傳 true,失敗時回傳 false

範例

範例 1 session_regenerate_id() 範例

<?php
// 注意:此程式碼並非完全可運作的程式碼,而是一個範例!

session_start();

// 檢查銷毀時間戳記
if (isset($_SESSION['destroyed'])
&&
$_SESSION['destroyed'] < time() - 300) {
// 通常不應該發生。這可能是攻擊或由於不穩定的網路所致。
// 移除此使用者 session 的所有驗證狀態。
remove_all_authentication_flag_from_active_sessions($_SESSION['userid']);
throw(new
DestroyedSessionAccessException);
}

$old_sessionid = session_id();

// 設定銷毀時間戳記
$_SESSION['destroyed'] = time(); // session_regenerate_id() 會儲存舊的 session 資料

// 單純呼叫 session_regenerate_id() 可能會導致遺失 session 等問題。
// 請參閱下一個範例。
session_regenerate_id();

// 新 session 不需要銷毀時間戳記
unset($_SESSION['destroyed']);

$new_sessionid = session_id();

echo
"舊 Session: $old_sessionid<br />";
echo
"新 Session: $new_sessionid<br />";

print_r($_SESSION);
?>

目前的 session 模組無法很好地處理不穩定的網路。您應該管理 session ID,以避免因 session_regenerate_id 而遺失 session。

範例 2 避免因 session_regenerate_id() 而遺失 session

<?php
// 注意:這段程式碼並非完整可執行的程式碼,而是一個範例!
// my_session_start() 和 my_session_regenerate_id() 避免了因網路不穩定而遺失 session 的問題。
// 此外,這段程式碼也可能防止攻擊者利用竊取的 session。

function my_session_start() {
session_start();
if (isset(
$_SESSION['destroyed'])) {
if (
$_SESSION['destroyed'] < time()-300) {
// 通常不應該發生這種情況。這可能是攻擊或由於網路不穩定。
// 移除此使用者 session 的所有身份驗證狀態。
remove_all_authentication_flag_from_active_sessions($_SESSION['userid']);
throw(new
DestroyedSessionAccessException);
}
if (isset(
$_SESSION['new_session_id'])) {
// 尚未完全過期。可能是由於網路不穩定導致 cookie 遺失。
// 再次嘗試設定正確的 session ID cookie。
// 注意:如果您想要移除身份驗證標記,請不要再次嘗試設定 session ID。
session_commit();
session_id($_SESSION['new_session_id']);
// 新的 session ID 應該存在
session_start();
return;
}
}
}

function
my_session_regenerate_id() {
// 當由於網路不穩定而未設定 session ID 時,需要新的 session ID 來設定正確的 session ID。
$new_session_id = session_create_id();
$_SESSION['new_session_id'] = $new_session_id;

// 設定銷毀時間戳記
$_SESSION['destroyed'] = time();

// 寫入並關閉目前的 session;
session_commit();

// 使用新的 session ID 啟動 session
session_id($new_session_id);
ini_set('session.use_strict_mode', 0);
session_start();
ini_set('session.use_strict_mode', 1);

// 新的 session 不需要它們
unset($_SESSION['destroyed']);
unset(
$_SESSION['new_session_id']);
}
?>

參見

新增筆記

使用者提供的筆記 22 則筆記

tedivm at tedivm dot com
8 年前
我寫了目前最高票的評論,並且想要補充一些內容。我之前評論中現有的程式碼以不安全的方式產生 nonce -

<?php
$_SESSION
['nonce'] = md5(microtime(true));
?>

由於 "microtime" 是可預測的,這使得暴力破解 nonce 變得容易得多。更好的選擇是使用隨機性的東西,例如 -

<?php
bin2hex
(openssl_random_pseudo_bytes(32))
?>
tedivm at tedivm dot com
15 年前
我為我正在進行的專案編寫了以下程式碼 - 它試圖解決重新產生的問題,並處理一些其他與 session 相關的事情。

我嘗試使其更通用且可用(例如,在完整版本中,它會針對不同類型的 session 問題拋出不同類型的例外),希望有人會發現它很有用。

<?php
function regenerateSession($reload = false)
{
// 此權杖用於表單以防止跨站偽造攻擊
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();

// 設定目前的 Session 在 1 分鐘後過期
$_SESSION['OBSOLETE'] = true;
$_SESSION['EXPIRES'] = time() + 60;

// 建立新的 Session,而不銷毀舊的 Session
session_regenerate_id(false);

// 獲取目前的 Session ID 並關閉兩個 Session,以允許其他腳本使用它們
$newSession = session_id();
session_write_close();

// 將 Session ID 設定為新的 ID,並重新啟動它
session_id($newSession);
session_start();

// 不希望這個 Session 過期
unset($_SESSION['OBSOLETE']);
unset(
$_SESSION['EXPIRES']);
}

function
checkSession()
{
try{
if(
$_SESSION['OBSOLETE'] && ($_SESSION['EXPIRES'] < time()))
throw new
Exception('嘗試使用已過期的 Session。');

if(!
is_numeric($_SESSION['user_id']))
throw new
Exception('沒有啟動 Session。');

if(
$_SESSION['IPaddress'] != $_SERVER['REMOTE_ADDR'])
throw new
Exception('IP 位址不符 (可能為 Session 劫持嘗試)。');

if(
$_SESSION['userAgent'] != $_SERVER['HTTP_USER_AGENT'])
throw new
Exception('使用者代理不符 (可能為 Session 劫持嘗試)。');

if(!
$this->loadUser($_SESSION['user_id']))
throw new
Exception('嘗試登入不存在的使用者,ID 為:' . $_SESSION['user_id']);

if(!
$_SESSION['OBSOLETE'] && mt_rand(1, 100) == 1)
{
$this->regenerateSession();
}

return
true;

}catch(
Exception $e){
return
false;
}
}

?>
ei dot rik dot ops dot tad at gmail dot com
8 年前
在 PHP 5.6 (以及可能更舊的版本) 中,session_regenerate_id(true) 不會觸發對新 session ID 的 session handler 的 read() 呼叫。

在 PHP 7 中,會在 session_regenerate_id(true) 期間觸發 read()。在處理自訂 session handler 時,知道這一點很好。
gr at gr5 dot org
13 年前
如果您嘗試維護 2 個活動的 Session,請不要使用 session_regenerate_id()。特別是當第一個 Session 關閉並且需要開啟第二個 Session 時。因為 Session ID 已被快取,所以您還必須在第二次明確設定它。

<?php
session_name
('PHPSESSID'); // 多餘的 - 這裡為了清晰起見
session_start();
// ...執行一些操作
session_write_close();

// 現在切換到 Session 2...
session_name('PHPSESSID_2');
if (isset(
$_COOKIE['phpsessid_2']))
session_id($_COOKIE['phpsessid_2']); // 不這樣做只會再次重新開啟第一個 Session
else
session_id(sha1(mt_rand()); // 此處不要使用 session_regenerate_id()。不建立新的 ID 將會建立兩個具有相同 Session ID 和相同 Session 變數的 Cookie
session_start();
// ...使用 Session 2 執行一些操作
session_write_close();
?>
ross at kndr dot org
20 年前
在之前的筆記中,php at 5mm de 描述如何透過以下方式防止 Session 劫持:
確保提供的 Session ID 符合首次發出 Session ID 時存在的 HTTP_USER_AGENT 和 REMOTE_ADDR 欄位。應該注意的是,HTTP_USER_AGENT 是由客戶端提供的,因此可以被惡意使用者輕易修改。此外,客戶端的 IP 位址也可以被偽造,儘管這比較困難。在依賴 Session 進行身份驗證時應謹慎。
spmtrap at yahoo dot com
12 年前
`session_regenerate_id` 會發送新的 Cookie,但不會覆寫儲存在 `$_COOKIE` 中的值。呼叫 `session_destroy` 後,開啟的 Session ID 會被丟棄,因此只需使用 `session_start` (如 Ben Johnson 的程式碼中所做的那樣) 重新啟動 Session,就會為目前的請求重新開啟原始的 (但現在是空的) Session (後續請求將使用新的 Session ID)。使用 `session_regenerate_id` 的 `$delete_old_session` 參數來刪除先前的 Session 資料,而不是 `session_destroy`+`session_start`。

<?php
session_start
();
/* 建立新的 Session,刪除先前的 Session 資料。 */
session_regenerate_id(TRUE);
/* 清除從先前 Session 帶來的資料 */
$_SESSION=array();
?>

要啟動新的 Session 並保持舊的 Session 不受影響,只需省略 `session_regenerate_id` 的引數。
php at cny dot de
19 年前
另請注意,如果使用者透過 Proxy 農場連線,則 REMOTE_ADDR 可能在每個請求中都會變更。大多數 AOL 使用者都會這樣。
babel at nosqamplease sympatico ca
20 年前
針對 php at 5mm de 的評論補充

如果 Session 是透過 https 持有的,則最好儲存客戶端的憑證或 SSL Session ID,而不是主機名稱或 IP,因為它是 Proxy 透明的且更安全。
k-gun !! mail
7 年前
根據 RFC,「session.use_strict_mode」的文件範例是錯誤的 (在 https://wiki.php.net/rfc/strict_sessions 上說:「當 use_strice_mode=1 時,session_id() 會發出警告錯誤」)。

因此,這個指令會影響「session_id()」而不是「session_start()」。所以用法必須像這樣:

<?php
// 首先設定 ini
ini_set('session.use_strict_mode', '0');
// 然後
session_id($sid);

// 然後
// 也許執行這個:ini_restore('session.use_strict_mode');

// 然後繼續...
?>

參考 (ctrl+f & use_strict_mode);
https://wiki.php.net/rfc/strict_sessions
https://wiki.php.net/rfc/session-create-id
https://php.dev.org.tw/manual/en/function.session-id.php#119997
madsen at sjovedyr.dk
21 年前
我遇到一個問題,當訪客的 session_id cookie 被代理伺服器更改時,他們在瀏覽我的網站時會收到大量的錯誤訊息。
我是這樣處理錯誤的 session id 的。(請注意:這只適用於 4.3.2 以上的版本。)

<?php
// 啟動 session 並抑制錯誤訊息。
@session_start();

// 捕捉錯誤的 session id。
if (!preg_match("/^[0-9a-z]*$/i", session_id())) {

// 輸出一個關於錯誤 session id 的警告。
$error->handleError("WARN", "您的 session id 有問題,您可能無法使用本網站的某些功能。");

// 產生一個新的 session id。
session_regenerate_id();
}

// 網站內容。
?>

希望對某些人有幫助。
Nicolas dot Chachereau at Infomaniak dot ch
19 年前
Session_destroy() 不僅會銷毀與目前 session_id 相關聯的資料(例如,如果您使用預設的 session 儲存處理器,則是檔案),還會銷毀 session 本身:如果您呼叫 session_destroy(),然後呼叫 session_regenerate_id(),它將會傳回 false,並且 session_id() 不會傳回任何東西。為了在銷毀 session 後操作 session,您需要重新啟動它。

所以事實上,chris 提到的程式碼將無法運作。如果您想銷毀與舊 session_id 相關聯的檔案,請嘗試以下方法
<?php
session_start
();
$old_sessid = session_id();
session_regenerate_id();
$new_sessid = session_id();
session_id($old_sessid);
session_destroy();

// 如果您不複製 $_SESSION 陣列,您將無法使用與舊 session id 相關聯的資料。
$old_session = $_SESSION;
session_id($new_sessid);
session_start();
$_SESSION = $old_session;
//...
?>

注意:此技術將發送 3 個 Set-Cookie 標頭(每個 session_start() 一個,以及 session_regenerate_id() 一個)。我不認為這是一個問題,但如果它看起來是個問題,您可以將其保留,等待垃圾收集器來捕捉與舊 session 關聯的檔案,或嘗試使用 unlink() 刪除該檔案。
Rumour
10 個月前
我提供的 my_session_regenerate_id() 程式碼範例無法運作,並且會銷毀所有 session 變數。此外,第二個 ini_set 會產生錯誤。

用於重新產生(僅此部分,其餘部分看起來沒問題)的程式碼應該只是這樣

<?php
function my_session_regenerate_id() {
$new_session_id = session_create_id();

// 備份 session 變數
$keepSession = $_SESSION ;

// 為連線不佳而未收到新 session id 的使用者新增資訊
$_SESSION['new_session_id'] = $new_session_id;
// 設定銷毀時間戳記
$_SESSION['destroyed'] = time();

// 寫入並關閉目前的 session;
session_commit() ;

// 使用新的 session ID 啟動 session
ini_set('session.use_strict_mode', 0);
session_id($new_session_id);
session_start();
$_SESSION = $keepSession ;
}
?>
soapergem at gmail dot com
16 年前
如果您不小心處理事情,這可能是一個非常危險的函數,因為即使它產生了一整組新的 session 資料,它也會保持舊資料「開啟」狀態,直到腳本終止,從而鎖定任何其他嘗試與舊 session id 同時運行的腳本。

最近,我遇到一種情況,我想要明確傳入一個 session ID,將該 session 的資料複製到一個*新*的 session 中,然後在新的 session 下繼續操作,從而允許其他腳本同時使用舊的 session。但我很快發現,這些「其他腳本」在第一個腳本完成之前不會執行--即使它已經啟動了一個新的 session--因為它保持舊的 session 開啟。

因此,如果您嘗試將 session 資料複製到新的 session 以釋放舊的 session 以便繼續同時使用,以下是一些程式碼,可確保不會踩到任何人的腳

<?php

// 取得現有 session 的 session id
$sid = $_GET['sid'];

// 啟動舊的 session 以檢索 $_SESSION 資料
session_id($sid);
session_start();

// 啟動新的 session;這會將 $_SESSION 資料複製過去
session_regenerate_id();

// 抓住新的 session id
$sid = session_id();

// 關閉舊的和新的 session
session_write_close();

// 重新開啟新的 session
session_id($sid);
session_start();

/* 主要程式碼在這裡 */

?>

這也可以封裝到一個帶有一個參數的函數中,以節省空間(如果這是重複的事情)。
primenetworkzx at gmail dot com
16 年前
如果您將 session 資料儲存在資料庫中,則必須手動更新資料庫中的 session_id。session_set_save_handler() 不會為您執行此操作。

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);
}

請務必將 session_regenerate_id() 設定為 FALSE,因為實際上沒有必要從 MySQL 中刪除整個記錄並再次新增它。這是多餘的開銷。只有更改 ID 才重要。
dyer85 at gmail dot com
19 年前
elger at NOSPAM dot yellowbee dot nl 在以下幾篇文章中可能會遇到潛在的問題。在程式碼中,使用了 REQUEST_URI 伺服器變數,在某些情況下,它可能已經包含查詢字串。因此,總是附加 '?whatever=foo' 有時會導致腳本故障。我建議使用 PHP_SELF,它不會在檔案之後包含查詢字串。
mikebranttx at gmail dot com
7 年前
請注意,在目前的 PHP 7.2 每日建置版本中,上面的範例 #2 無法按所示運作。在 session_start() 之後嘗試重新開啟嚴格模式時,您會收到以下錯誤

「ini_set():session 處於活動狀態。您目前無法變更 session 模組的 ini 設定」

我想這表示對於任何您執行 session ID 重新產生或 session ID 轉發(從最近執行 session ID 重新產生的 session 到新的 session ID)的 session。您將不得不接受在該活動 session 的剩餘時間內停用嚴格模式。

只要您遵循每個請求單一 session 的設計(即您不是使用多個並行 session),我不認為這真的是一個安全性問題。
chris at knowledge dot tee-vee
19 年前
licp - 不,session_regenerate_id() 不會銷毀任何已儲存的 session 資料。

elger,我比較喜歡以下順序

[程式碼]
// 使用目前 session_id 的任何先前儲存的 session 資料來填入 $_SESSION
session_start();
...
// 刪除與目前 session_id 相關聯的任何已儲存資料,$_SESSION 不會變更
session_destroy();

// 變更 session_id,$_SESSION 不會變更
session_regenerate_id();
...
// 將任何 $_SESSION 資料儲存在目前的 session_id 下
session_close();
[/程式碼]
antomsa at hotmail dot it
3 年前
<?php

function my_session_regenerate_id() {
...
session_start();
...

unset(
$_SESSION['destroyed']);
unset(
$_SESSION['new_session_id']);
}

?>

在 my_session_regenerate_id() 內,unset($_SESSION['destroyed']) 和
unset($_SESSION['new_session_id']) 是沒用的,因為 session_start() 將會開啟一個空的 session。如果呼叫內建的 session_regenerate_id(),則會需要它們。
kgm_india at yahoo dot co dot in
11 年前
就我從網路上閱讀到的這些筆記所理解的,

session_name() 是透過 cookie 或 http 連結識別為 session a 的名稱。

session_id 就像 session_name() 內的一個交易,一個 session_name 可能有多個 session_id

每個 session_id 都有對應的儲存資料。

session_id 用於 session_name 下的讀取和寫入回呼

無論如何,請先呼叫
session_name(),
然後呼叫 session_id ()
然後呼叫 start_session()

start_session 將會開啟 session_name,然後檢查先前呼叫的 session_id,並在讀取或寫入回呼中使用它來儲存或檢索資料

在沒有 session_name 或 session_id 的情況下呼叫 start_session() 將會依序使用預設的 session_name 和預設的 session_id

我希望如果遵循該順序,則不應該有任何問題。

如果您明確要使用它們,請不要在 start_session() 之後呼叫 session_name 或 session_id。

感謝其他人的筆記。
raido dot aasoja at gmail dot com
14 年前
關於 session 遺失以及嘗試使用 session_regenerate_id 修復它的注意事項
請確保您沒有嘗試將 SimpleXML 物件推送至 session。如果不先將其轉換為陣列,則它將無法運作。 :)
sopel
19 年前
對於 php 5.1 以上的使用者,可能值得參考這個連結:http://ilia.ws/archives/47-session_regenerate_id-Improvement.html
Alamin
6 年前
在 php.net 提供的第二個範例中,是否應該在 session regenerate 方法內呼叫 my_session_start,而不是直接呼叫 session_start?我認為這是個錯誤。
To Top