# Session security

## Why this matters

A session id is a bearer credential: whoever presents it is treated as the
user it belongs to. Two distinct failures let an attacker end up holding a
victim's session id: fixation, where the attacker sets the session id before
the victim authenticates and it is never replaced, and hijacking, where the
attacker steals an already-authenticated session id after the fact (via
script injection, packet sniffing, or physical access). php.net's own
session-security guidance treats both as core, not edge-case, concerns, and
documents specific configuration and code-ordering requirements to close
each path.

## NORMATIVE: regenerate the session id on privilege change

Per the php.net manual's session-management-basics guidance:

- "Session IDs **must** be regenerated when user privileges are elevated,
  such as after authenticating. `session_regenerate_id()` must be called
  prior to setting the authentication information to `$_SESSION`."
  `session_regenerate_id()` saves the current session data automatically, so
  the call itself does not lose session state — but it must come **before**
  the authenticated flag is written, so that only the new session id carries
  it.
- More generally: "Session ID regeneration reduces the risk of stolen
  session IDs, thus `session_regenerate_id()` must be called periodically.
  E.g. Regenerate the session ID every 15 minutes for security sensitive
  content."
- `session_regenerate_id()` does **not** delete the outdated session's data
  by default. The manual is explicit: "`session_regenerate_id()` does not
  delete outdated sessions by default. Obsolete authenticated sessions may
  be present for use. Developers must prevent outdated sessions to be
  consumed by anyone." A regeneration call without also invalidating the
  prior session's stored data leaves a still-valid, still-authenticated old
  session id usable by anyone who captured it before regeneration.

## NORMATIVE: `session.use_strict_mode` is mandatory and off by default

Per the manual: "enabling `session.use_strict_mode` is mandatory for secure
sessions. It is disabled by default. This prevents the session module [from
using] an uninitialized session ID. Put differently, the session module only
accepts valid session IDs generated by the session module. It rejects any
session ID supplied by users." With strict mode enabled, "an uninitialized
session ID is rejected and a new one is created. This prevents an attack that
forces users to use a known session ID" — the core session-fixation vector,
where an attacker "may paste links or send emails that contain the session
ID" and hope the victim's session simply adopts it.

Because this directive is **disabled by default**, a review must check for
an explicit `session.use_strict_mode=1` (or equivalent `ini_set()` call) —
absence of an error or warning is not evidence it is on.

## NORMATIVE: cookie-hardening directives

Per the php.net manual's session INI-settings guidance, four directives
harden the session cookie itself:

- **`session.cookie_httponly`** — "Refuses access to the session cookie from
  JavaScript. This setting prevents cookies snatched by a JavaScript
  injection." The manual states: "Almost all applications must use the
  httponly attribute for the session ID cookie."
- **`session.cookie_secure`** — "Allow access to the session ID cookie only
  when the protocol is HTTPS. If a website is only accessible via HTTPS, it
  should enable this setting." (The manual separately notes HSTS should be
  considered for HTTPS-only sites.)
- **`session.cookie_samesite`** — "As of PHP 7.3 the `SameSite` attribute can
  be set for the session ID cookie. This attribute is a way to mitigate CSRF
  (Cross Site Request Forgery) attacks." `Lax` allows the cookie on a
  cross-site GET navigation; `Strict` does not send it cross-site at all —
  the manual frames the choice as that accessibility trade-off, not a
  recommendation of one value for every application.
- Session hijacking generally (not fixation) is mitigated, per the manual,
  by transport: "There are several ways to leak an existing session ID to
  third parties. E.g. JavaScript injections, session IDs in URLs, packet
  sniffing, physical access to the device, etc... The solution is to
  implement SSL/TLS on the server and make it mandatory for users" —
  `session.cookie_secure` is the configuration lever that enforces this for
  the session cookie specifically.

## Reviewer evidence criteria

For every authentication, privilege-elevation, or password-reset path:

- Confirm `session_regenerate_id(true)` is called, and confirm it is called
  **before** the code writes the authenticated/elevated-privilege flag into
  `$_SESSION`. A call present anywhere in the function is not sufficient —
  check the ordering.
- Confirm the prior session's data is invalidated or is not relied upon as
  still-authenticated after regeneration; a regeneration call with the old
  session id left independently valid and authenticated is a finding.

For the effective session configuration in scope (`php.ini`, `.htaccess`,
`ini_set()` calls, or a framework's session configuration layer):

- Confirm `session.use_strict_mode` is explicitly enabled; do not treat its
  absence from configuration as acceptable, since it is disabled by default.
- Confirm `session.cookie_httponly` is enabled.
- Confirm `session.cookie_secure` is enabled for any application served over
  HTTPS (and flag its absence as a finding for an HTTPS-only application).
- Confirm `session.cookie_samesite` is set to `Lax` or `Strict` (requires
  PHP 7.3+); flag an unset value, and flag PHP < 7.3 as unable to set this
  directive at all (a version-upgrade finding, not a configuration finding).

Treat a missing `session_regenerate_id()` call (or one ordered after the
authenticated flag is set) and a missing/disabled `session.use_strict_mode`
as blocking findings. Treat missing `cookie_httponly`, `cookie_secure`, or
`cookie_samesite` as findings that escalate to blocking when combined with
any independently confirmed session-id-leak or fixation path.

## Applicable versions

`session.cookie_samesite` requires PHP 7.3 or later — on an earlier version
this is a version-upgrade finding rather than a configuration omission. The
other directives and the `session_regenerate_id()` ordering guidance are
current php.net manual guidance as of this review; re-verify against the
live manual page before citing behavior for a PHP version outside current
support.

## Sources

- [PHP Manual — Session Management Basics](https://www.php.net/manual/en/features.session.security.management.php) — supports the `session_regenerate_id()` ordering requirement around privilege elevation, the periodic-regeneration guidance, and the "does not delete outdated sessions by default" caution.
- [PHP Manual — Securing Session INI Settings](https://www.php.net/manual/en/session.security.ini.php) — supports `session.use_strict_mode` being mandatory-but-disabled-by-default, `session.cookie_httponly`, `session.cookie_secure`, and `session.cookie_samesite` (PHP 7.3+) directive behavior.
- [PHP Manual — Sessions and Security (overview)](https://www.php.net/manual/en/session.security.php) — top-level page framing session security as core to web application security and pointing to the two subsection pages above.

Last verified: 2026-07-16.
