In PHP, what do session_regenerate_id() and session.use_strict_mode each protect against, and what are their defaults?
answer
- session fixation, two angles
- new ID, same $_SESSION data
- delete_old_session defaults to false
- use_strict_mode = 0 out of the box
- reject IDs the handler never issued
basics
~10 sBoth defend against session fixation. session_regenerate_id() swaps the ID after a privilege change such as login, keeping the data; session.use_strict_mode, off by default, makes session_start() refuse IDs the server never issued.
solid answer
~40 sIn **session fixation** an attacker gets a victim to use a session ID the attacker knows, then rides the session once the victim logs in. `session_regenerate_id()` breaks that by giving the active session a new ID, and a new cookie, after login or any privilege change; `$_SESSION` keeps its data. Its only parameter, `delete_old_session`, defaults to `false`, so the old record is written and stays in storage until garbage collection unless you pass `true`. `session.use_strict_mode` defaults to `0` in php-src and in both shipped php.ini files. Set to `1`, it makes `session_start()` reject an ID the save handler reports as unknown and issue a fresh one, which stops an attacker from simply inventing an ID. The two are complementary: strict mode blocks made-up IDs, regeneration blocks real ones the attacker obtained first.
code
php · 13 lines<?php
declare(strict_types=1);
session_start(['use_strict_mode' => 1]); // or session.use_strict_mode=1 in php.ini
$userId = findUserIdByLogin($_POST['email'] ?? '', $_POST['password'] ?? '');
if ($userId !== null) {
session_regenerate_id(true); // new ID and cookie, same data, old record removed
$_SESSION['user_id'] = $userId;
header('Location: /account');
exit;
}go deeper
Recall that the session ID should change after login with session_regenerate_id(), and that the session data stays in place when it does.
Explain session fixation and adoption, the delete_old_session parameter and its false default, and how strict mode rejects IDs the handler does not know.
Show that you enable strict mode everywhere, regenerate on every privilege change, and understand how deleting the old record interacts with concurrent requests.
Frame the grace-period choice for old IDs as a security versus reliability trade-off and set one policy that every application and custom handler follows.
## The attack both settings target **Session fixation** reverses the usual theft. Instead of stealing a victim's session ID, the attacker *chooses* one and gets the victim's browser to use it, for example through a crafted link on a site that accepts IDs from the URL, or a cookie set from a sibling subdomain. The victim then logs in. If the server keeps the same ID across login, the attacker's copy of that ID is now an authenticated session. PHP has two tools for this, and they cover different halves of the problem. ## session.use_strict_mode: refuse IDs the server never issued By default PHP accepts whatever ID the browser presents. If no session exists under that ID, `session_start()` simply creates an empty one with that exact ID. This is called **session adoption**, and it lets an attacker pick any string as the victim's future ID. With `session.use_strict_mode=1`: 1. `session_start()` asks the save handler whether the presented ID exists (the handler's validate step). 2. If it does not, PHP discards it, generates a new ID and sends a new cookie. 3. Only IDs that the server itself created and stored are ever used. The **default is `0`** in the engine and in both `php.ini-production` and `php.ini-development`; the shipped ini comment says enabling it is encouraged. The `files` handler validates by checking whether the `sess_<id>` file exists. A custom handler must implement `validateId()` from `SessionUpdateTimestampHandlerInterface`; without it, the fallback validator accepts every ID and strict mode does nothing. ## session_regenerate_id(): a new ID after a privilege change Strict mode cannot help if the attacker first visits the site, receives a *genuine* ID, and plants that one. The defence is to change the ID when the session's privileges change, above all right after a successful login. `session_regenerate_id(bool $delete_old_session = false): bool`: - requires an **active session** and headers not yet sent; otherwise it warns and returns `false`; - creates a new ID through the handler and queues a new session cookie; - **keeps `$_SESSION`**: the data continues under the new ID; - with the default `false`, writes the current data to the **old** record and leaves it in storage until garbage collection removes it; - with `true`, destroys the old record immediately. | | `session.use_strict_mode` | `session_regenerate_id()` | |---|---|---| | Kind | ini setting | function call | | Default | `0` (off) | `delete_old_session = false` | | Stops | IDs the attacker invented | IDs the attacker obtained legitimately | | When it acts | every `session_start()` | where you call it, e.g. after login | | Needs from a custom handler | `validateId()` | a working create and write | ## Choosing delete_old_session Passing `true` removes the old record at once, so a stolen old ID stops working immediately. The cost is concurrency: a parallel request still carrying the old ID, such as an AJAX call fired just before the login response arrived, finds no record and starts empty (or, with strict mode, gets a new ID). The PHP manual therefore suggests not deleting immediately but storing a destroyed-at timestamp in the old session and refusing it after a short grace period. Which trade-off fits is a session-lifecycle design decision; the PHP facts are the parameter, its `false` default and what each value does. ## Putting it together - Enable `session.use_strict_mode=1` in php.ini for every application. - Call `session_regenerate_id()` immediately after authentication succeeds and before writing the user ID into `$_SESSION`. - Keep `session.use_only_cookies=1`, its default, so IDs never come from URLs. - If you write your own save handler, implement `validateId()`, or strict mode silently stops protecting you. ## Mistakes interviewers listen for - **Regenerating on every request.** It multiplies old records, races with parallel requests and buys little; regenerate on privilege changes, not as a heartbeat. - **Regenerating at the wrong moment.** Rotating when the login form is shown leaves room to plant an ID afterwards; the rotation that counts happens in the same request that marks the session authenticated. - **Calling it after output.** A redirect helper that already echoed something makes `session_regenerate_id()` warn and return `false`; check the return value instead of assuming it worked. - **Assuming it clears data.** Anything the anonymous session held, such as a basket or a return URL, is carried into the authenticated one. Usually that is wanted; when it is not, reset `$_SESSION` explicitly. - **Turning on strict mode and nothing else.** Strict mode is a filter on incoming IDs; it does not rotate anything.
- Why does session.use_strict_mode do nothing with some custom save handlers?Strict mode asks the handler whether an ID exists. A handler object only answers if it implements `validateId()` from `SessionUpdateTimestampHandlerInterface`; otherwise PHP's fallback validator reports every ID as valid, so unknown IDs are adopted exactly as with strict mode off.
- What happens if session_regenerate_id() is called before session_start() or after output was sent?It emits a warning and returns `false` without changing anything. It needs an active session to copy data from, and it must send a new cookie, so headers must still be unsent. Call it after `session_start()` and before any echo or redirect.
- Does session_regenerate_id() clear $_SESSION?No. The data carries over to the new ID unchanged. If you want to drop pre-login data, such as anonymous preferences, you clear or rebuild `$_SESSION` yourself before or after regenerating.
saying these in an interview costs you the question
- session_regenerate_id() empties $_SESSION, so data must be copied first
- session_regenerate_id() deletes the old session record by default
- session.use_strict_mode is already on in php.ini-production
- Strict mode alone makes regenerating the ID on login unnecessary
- Strict mode protects any custom save handler without extra methods