我寫了目前最高票的評論,並且想要補充一些內容。我之前評論中現有的程式碼以不安全的方式產生 nonce -
<?php
$_SESSION['nonce'] = md5(microtime(true));
?>
由於 "microtime" 是可預測的,這使得暴力破解 nonce 變得容易得多。更好的選擇是使用隨機性的東西,例如 -
<?php
bin2hex(openssl_random_pseudo_bytes(32))
?>
(PHP 4 >= 4.3.2, PHP 5, PHP 7, PHP 8)
session_regenerate_id — 使用新產生的 ID 更新目前的 session ID
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。
範例 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']);
}
?>
我寫了目前最高票的評論,並且想要補充一些內容。我之前評論中現有的程式碼以不安全的方式產生 nonce -
<?php
$_SESSION['nonce'] = md5(microtime(true));
?>
由於 "microtime" 是可預測的,這使得暴力破解 nonce 變得容易得多。更好的選擇是使用隨機性的東西,例如 -
<?php
bin2hex(openssl_random_pseudo_bytes(32))
?>
我為我正在進行的專案編寫了以下程式碼 - 它試圖解決重新產生的問題,並處理一些其他與 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;
}
}
?>
在 PHP 5.6 (以及可能更舊的版本) 中,session_regenerate_id(true) 不會觸發對新 session ID 的 session handler 的 read() 呼叫。
在 PHP 7 中,會在 session_regenerate_id(true) 期間觸發 read()。在處理自訂 session handler 時,知道這一點很好。
如果您嘗試維護 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();
?>
在之前的筆記中,php at 5mm de 描述如何透過以下方式防止 Session 劫持:
確保提供的 Session ID 符合首次發出 Session ID 時存在的 HTTP_USER_AGENT 和 REMOTE_ADDR 欄位。應該注意的是,HTTP_USER_AGENT 是由客戶端提供的,因此可以被惡意使用者輕易修改。此外,客戶端的 IP 位址也可以被偽造,儘管這比較困難。在依賴 Session 進行身份驗證時應謹慎。
`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 5mm de 的評論補充
如果 Session 是透過 https 持有的,則最好儲存客戶端的憑證或 SSL Session ID,而不是主機名稱或 IP,因為它是 Proxy 透明的且更安全。
根據 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
我遇到一個問題,當訪客的 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();
}
// 網站內容。
?>
希望對某些人有幫助。
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() 刪除該檔案。
我提供的 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 ;
}
?>
如果您不小心處理事情,這可能是一個非常危險的函數,因為即使它產生了一整組新的 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();
/* 主要程式碼在這裡 */
?>
這也可以封裝到一個帶有一個參數的函數中,以節省空間(如果這是重複的事情)。
如果您將 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 才重要。
elger at NOSPAM dot yellowbee dot nl 在以下幾篇文章中可能會遇到潛在的問題。在程式碼中,使用了 REQUEST_URI 伺服器變數,在某些情況下,它可能已經包含查詢字串。因此,總是附加 '?whatever=foo' 有時會導致腳本故障。我建議使用 PHP_SELF,它不會在檔案之後包含查詢字串。
請注意,在目前的 PHP 7.2 每日建置版本中,上面的範例 #2 無法按所示運作。在 session_start() 之後嘗試重新開啟嚴格模式時,您會收到以下錯誤
「ini_set():session 處於活動狀態。您目前無法變更 session 模組的 ini 設定」
我想這表示對於任何您執行 session ID 重新產生或 session ID 轉發(從最近執行 session ID 重新產生的 session 到新的 session ID)的 session。您將不得不接受在該活動 session 的剩餘時間內停用嚴格模式。
只要您遵循每個請求單一 session 的設計(即您不是使用多個並行 session),我不認為這真的是一個安全性問題。
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();
[/程式碼]
<?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(),則會需要它們。
就我從網路上閱讀到的這些筆記所理解的,
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。
感謝其他人的筆記。
關於 session 遺失以及嘗試使用 session_regenerate_id 修復它的注意事項
請確保您沒有嘗試將 SimpleXML 物件推送至 session。如果不先將其轉換為陣列,則它將無法運作。 :)
對於 php 5.1 以上的使用者,可能值得參考這個連結:http://ilia.ws/archives/47-session_regenerate_id-Improvement.html