登入頁面同時載入表單 (CSRF Token) 與驗證碼圖片,兩個請求並發存取 Session,導致:
PHP 預設行為:
session_start() → 讀取 Session 檔案/Redis 並加鎖$_SESSION['x'] = 'y' 只操作記憶體陣列session_write_close() → 寫回存儲並釋放鎖關鍵點:單一請求只讀寫一次,鎖保護整個請求週期。
請求 A (表單頁) 請求 B (驗證碼圖)
───────────────── ─────────────────
session_start() ──────► 等待鎖 (阻塞)
讀取 Session
產生 CSRF Token
$_SESSION['csrf'] = 'abc'
session_start() (鎖釋放後)
讀取 Session (無 csrf!)
產生 Captcha
$_SESSION['captcha'] = '1234'
寫回、釋放鎖
寫回、釋放鎖 (覆蓋 captcha!)
結果:請求 A 寫回時覆蓋請求 B 的 captcha,或反之,導致資料遺失。
在輸出內容前呼叫 session_write_close(),後續邏輯不再寫入 Session:
// 視圖渲染完成後
$eventManager->attach('view:afterRender', function() {
session_write_close();
});
// 登入驗證成功後重導向前
$eventManager->attach('dispatch:afterDispatchLoop', function() {
session_write_close();
});
Redis 可用 Lua 腳本實現 CAS (Compare-And-Swap) 無鎖寫入,或使用 session_set_save_handler 自訂存取邏輯。
session_write_close() 於視圖渲染後INFO 中的 keyspace_hits/misses 等指標)