session_regenerate_id()
debt(d5/e1/b3/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list semgrep and phpstan as the tools that catch missing or incorrectly called session_regenerate_id() — neither is a default linter bundled with PHP, so this lands at d5. The specific pattern (no regeneration after login, or regeneration without true) requires a configured SAST rule and won't be caught by a bare compiler or syntax check.
Closest to 'one-line patch or single-call swap' (e1). The quick_fix is explicit: replace session_regenerate_id() with session_regenerate_id(true) — a single-character/token change at the call site. Even adding the call where it's missing is a one-line insertion immediately after login. No refactor or cross-file change required.
Closest to 'localised tax' (b3). The concept applies only to web contexts (applies_to: web) and specifically to login/privilege-escalation code paths. It doesn't shape the broader architecture — it's a localised concern confined to authentication logic. Future maintainers only need to think about it when touching session or auth code.
Closest to 'serious trap — contradicts how a similar concept works elsewhere' (t7). The misconception field captures the exact trap: session_regenerate_id() without the boolean parameter looks complete and correct — the function name implies the old session is replaced — but the old session file silently persists, leaving the fixation vulnerability fully open. A competent developer naturally assumes calling the function is sufficient, making this a serious behavioural contradiction between what the name implies and what actually happens.
Also Known As
TL;DR
Explanation
session_regenerate_id(true) creates a new session ID and optionally deletes the old session file (the true argument). It must be called immediately after successful authentication. Without it, an attacker who knows a victim's pre-login session ID (e.g. from a shared computer or network sniff) can use the same ID after the victim logs in and gain authenticated access. The true parameter is important — without it the old session file persists and is still usable.
Common Misconception
Why It Matters
Common Mistakes
- Not passing true to session_regenerate_id() — the old session file persists without the delete flag.
- Calling session_regenerate_id() before session_start() — has no effect.
- Not regenerating after privilege level changes — elevating to admin without regenerating is still a fixation risk.
- Regenerating on every request — unnecessary overhead and can break concurrent AJAX requests.
Code Examples
// Login without session regeneration — fixation vulnerability:
session_start();
if (authenticate($user, $pass)) {
$_SESSION['user_id'] = $user->id;
// Missing: session_regenerate_id(true);
header('Location: /dashboard');
}
// Regenerate session ID after any privilege change — prevents session fixation
session_start();
$user = User::where('email', $email)->first();
if (!$user || !password_verify($password, $user->password)) abort(401);
// Regenerate BEFORE writing auth data
session_regenerate_id(true); // true = delete old session file
$_SESSION['user_id'] = $user->id;
$_SESSION['role'] = $user->role;
// On logout:
session_regenerate_id(true);
session_destroy();
// Also regenerate on privilege escalation:
// sudo, 2FA confirmation, admin impersonation, etc.