Session Fixation

Session Fixation, Regeneration and Logout

In session fixation an attacker plants a known ID in the victim's browser and uses it after login. use_strict_mode = 1 replaces unknown IDs (with it off, this server adopted attacker000…1), but an existing ID passes, so also change the ID at login and logout.

login.php: regenerate on login, destroy completely on logoutPHP
<?php
declare(strict_types=1);
session_start();
if (($_GET['do'] ?? '') === 'login') {      // only after password_verify() succeeds (4.17.9)
  session_regenerate_id(true);             // new ID; true deletes the old session file
  $_SESSION['uid'] = 42;
} elseif (($_GET['do'] ?? '') === 'logout') {
  $_SESSION = [];
  $cookie = session_get_cookie_params();
  unset($cookie['lifetime']);              // setcookie() wants 'expires', not 'lifetime'
  setcookie(session_name(), '', $cookie);
  session_destroy();
}
echo 'id=', session_id() ?: '(none)', ' uid=', $_SESSION['uid'] ?? '-', "\n";
Output
$ curl -s -c auth.txt -b PHPSESSID=attacker0000000000000000000000001 127.0.0.1:8215/login.php
id=c72fcda3ad82cdc3baca027196535f37 uid=-
$ curl -s -b auth.txt -c auth.txt '127.0.0.1:8215/login.php?do=login'
id=c5c6f5a49fa3f3466d91bc568c8ee82a uid=42
$ curl -si -b auth.txt -c auth.txt '127.0.0.1:8215/login.php?do=logout' | grep -E '^(Set|id)' \
  | fold_cookies
Set-Cookie: PHPSESSID=deleted;
    expires=Thu, 01 Jan 1970 00:00:01 GMT; Max-Age=0;
    path=/; secure; HttpOnly; SameSite=Lax
id=(none) uid=-

The planted ID was replaced, and login moved the data to a fresh ID and deleted the old file. Logout needs all three steps; session_destroy() alone leaves the cookie and $_SESSION in place. Auth Hardening covers the rest of session hardening.