From c4270f0aa5097424d9b98943e726c4142e96fc1b Mon Sep 17 00:00:00 2001 From: Jorg Sowa Date: Sun, 3 May 2026 14:48:39 +0200 Subject: [PATCH 1/4] ext/session: secure session configuration defaults (PHP 8.6 RFC) Implements the "Secure Session Configuration Defaults" RFC by changing three INI defaults to provide secure session behavior out of the box: - session.use_strict_mode: 0 -> 1 (mitigates session fixation) - session.cookie_httponly: 0 -> 1 (mitigates XSS access to session cookie) - session.cookie_samesite: "" -> "Lax" (mitigates CSRF) RFC: https://wiki.php.net/rfc/session_security_defaults --- NEWS | 3 +++ UPGRADING | 18 ++++++++++++++++++ ext/session/session.c | 6 +++--- php.ini-development | 9 ++++----- php.ini-production | 9 ++++----- 5 files changed, 32 insertions(+), 13 deletions(-) diff --git a/NEWS b/NEWS index 1200d086067c..7bae147af6df 100644 --- a/NEWS +++ b/NEWS @@ -156,6 +156,9 @@ PHP NEWS (Girgias) . Null bytes in session.cookie_path, session.cookie_domain, and session.cache_limiter are now rejected with a warning. (jorgsowa) + . Changed defaults of session.use_strict_mode (now 1), session.cookie_httponly + (now 1) and session.cookie_samesite (now "Lax") to provide secure session + behavior out of the box. (RFC: Secure Session Configuration Defaults) - Soap: . Soap::__setCookie() when cookie name is a digit is now not stored and diff --git a/UPGRADING b/UPGRADING index 1bba53274c16..a3b22d8c0560 100644 --- a/UPGRADING +++ b/UPGRADING @@ -94,6 +94,24 @@ PHP 8.6 UPGRADE NOTES comparison. Custom session handlers that rely on write() being called with empty data (e.g. to destroy the session) should implement the same logic in their updateTimestamp() method. + . The defaults of three session INI settings have changed to provide secure + behavior out of the box: + - session.use_strict_mode is now 1 (was 0). Strict mode rejects + uninitialized session IDs, mitigating session fixation. Custom session + handlers that previously relied on accepting externally supplied IDs + without a corresponding storage entry must either implement + validateId() / create_sid() or explicitly set this to 0. + - session.cookie_httponly is now 1 (was 0). Session cookies are no + longer accessible to JavaScript via document.cookie. Applications + that read the session cookie from JavaScript must explicitly set + this to 0. + - session.cookie_samesite is now "Lax" (was unset). Session cookies + are no longer sent on cross-site requests other than top-level + navigations using safe HTTP methods. Applications that depend on + session cookies being sent on cross-site POST submissions must + explicitly set this to "None" (and also set session.cookie_secure + to 1). + RFC: https://wiki.php.net/rfc/session_security_defaults - SPL: . SplObjectStorage::getHash() implementations may no longer mutate any diff --git a/ext/session/session.c b/ext/session/session.c index aa6cfb311afa..1fdfc5d1073f 100644 --- a/ext/session/session.c +++ b/ext/session/session.c @@ -928,11 +928,11 @@ PHP_INI_BEGIN() STD_PHP_INI_ENTRY("session.cookie_domain", "", PHP_INI_ALL, OnUpdateSessionStr, cookie_domain, php_ps_globals, ps_globals) STD_PHP_INI_BOOLEAN("session.cookie_secure", "0", PHP_INI_ALL, OnUpdateSessionBool, cookie_secure, php_ps_globals, ps_globals) STD_PHP_INI_BOOLEAN("session.cookie_partitioned", "0", PHP_INI_ALL, OnUpdateSessionBool, cookie_partitioned, php_ps_globals, ps_globals) - STD_PHP_INI_BOOLEAN("session.cookie_httponly", "0", PHP_INI_ALL, OnUpdateSessionBool, cookie_httponly, php_ps_globals, ps_globals) - STD_PHP_INI_ENTRY("session.cookie_samesite", "", PHP_INI_ALL, OnUpdateSessionSameSite, cookie_samesite, php_ps_globals, ps_globals) + STD_PHP_INI_BOOLEAN("session.cookie_httponly", "1", PHP_INI_ALL, OnUpdateSessionBool, cookie_httponly, php_ps_globals, ps_globals) + STD_PHP_INI_ENTRY("session.cookie_samesite", "Lax", PHP_INI_ALL, OnUpdateSessionSameSite, cookie_samesite, php_ps_globals, ps_globals) STD_PHP_INI_BOOLEAN("session.use_cookies", "1", PHP_INI_ALL, OnUpdateSessionBool, use_cookies, php_ps_globals, ps_globals) STD_PHP_INI_BOOLEAN("session.use_only_cookies", "1", PHP_INI_ALL, OnUpdateUseOnlyCookies, use_only_cookies, php_ps_globals, ps_globals) - STD_PHP_INI_BOOLEAN("session.use_strict_mode", "0", PHP_INI_ALL, OnUpdateSessionBool, use_strict_mode, php_ps_globals, ps_globals) + STD_PHP_INI_BOOLEAN("session.use_strict_mode", "1", PHP_INI_ALL, OnUpdateSessionBool, use_strict_mode, php_ps_globals, ps_globals) STD_PHP_INI_ENTRY("session.referer_check", "", PHP_INI_ALL, OnUpdateRefererCheck, extern_referer_chk, php_ps_globals, ps_globals) STD_PHP_INI_ENTRY("session.cache_limiter", "nocache", PHP_INI_ALL, OnUpdateSessionStr, cache_limiter, php_ps_globals, ps_globals) STD_PHP_INI_ENTRY("session.cache_expire", "180", PHP_INI_ALL, OnUpdateSessionLong, cache_expire, php_ps_globals, ps_globals) diff --git a/php.ini-development b/php.ini-development index ee75459ea56c..78ae50708d5a 100644 --- a/php.ini-development +++ b/php.ini-development @@ -1305,10 +1305,9 @@ session.save_handler = files ; Strict session mode does not accept an uninitialized session ID, and ; regenerates the session ID if the browser sends an uninitialized session ID. ; Strict mode protects applications from session fixation via a session adoption -; vulnerability. It is disabled by default for maximum compatibility, but -; enabling it is encouraged. +; vulnerability. ; https://wiki.php.net/rfc/strict_sessions -session.use_strict_mode = 0 +session.use_strict_mode = 1 ; Whether to use cookies. ; https://php.net/session.use-cookies @@ -1350,13 +1349,13 @@ session.cookie_domain = ; Whether or not to add the httpOnly flag to the cookie, which makes it ; inaccessible to browser scripting languages such as JavaScript. ; https://php.net/session.cookie-httponly -session.cookie_httponly = +session.cookie_httponly = 1 ; Add SameSite attribute to cookie to help mitigate Cross-Site Request Forgery (CSRF/XSRF) ; Current valid values are "Strict", "Lax" or "None". When using "None", ; make sure to include the quotes, as `none` is interpreted like `false` in ini files. ; https://tools.ietf.org/html/draft-west-first-party-cookies-07 -session.cookie_samesite = +session.cookie_samesite = "Lax" ; Handler used to serialize data. php is the standard serializer of PHP. ; https://php.net/session.serialize-handler diff --git a/php.ini-production b/php.ini-production index b10e2ba9944a..eb6880fe75d6 100644 --- a/php.ini-production +++ b/php.ini-production @@ -1307,10 +1307,9 @@ session.save_handler = files ; Strict session mode does not accept an uninitialized session ID, and ; regenerates the session ID if the browser sends an uninitialized session ID. ; Strict mode protects applications from session fixation via a session adoption -; vulnerability. It is disabled by default for maximum compatibility, but -; enabling it is encouraged. +; vulnerability. ; https://wiki.php.net/rfc/strict_sessions -session.use_strict_mode = 0 +session.use_strict_mode = 1 ; Whether to use cookies. ; https://php.net/session.use-cookies @@ -1352,13 +1351,13 @@ session.cookie_domain = ; Whether or not to add the httpOnly flag to the cookie, which makes it ; inaccessible to browser scripting languages such as JavaScript. ; https://php.net/session.cookie-httponly -session.cookie_httponly = +session.cookie_httponly = 1 ; Add SameSite attribute to cookie to help mitigate Cross-Site Request Forgery (CSRF/XSRF) ; Current valid values are "Strict", "Lax" or "None". When using "None", ; make sure to include the quotes, as `none` is interpreted like `false` in ini files. ; https://tools.ietf.org/html/draft-west-first-party-cookies-07 -session.cookie_samesite = +session.cookie_samesite = "Lax" ; Handler used to serialize data. php is the standard serializer of PHP. ; https://php.net/session.serialize-handler From b2414fe35106f4af18811a4f1019a1ae22f5f449 Mon Sep 17 00:00:00 2001 From: Jorg Sowa Date: Sun, 17 May 2026 17:40:14 +0200 Subject: [PATCH 2/4] test: adjusted test files --- ext/session/tests/bug74892.phpt | 1 + ext/session/tests/bug80774.phpt | 2 +- ext/session/tests/gh9200.phpt | 3 ++- ext/session/tests/mod_files/gc_dirdepth2.phpt | 1 + ext/session/tests/mod_files/gc_dirdepth_disabled.phpt | 1 + ext/session/tests/mod_files/gc_dirdepth_selective.phpt | 1 + ext/session/tests/session_regenerate_id_cookie.phpt | 6 +++--- .../tests/session_set_cookie_params_invalid_ini.phpt | 2 +- ext/session/tests/session_start_partitioned_headers.phpt | 2 +- 9 files changed, 12 insertions(+), 7 deletions(-) diff --git a/ext/session/tests/bug74892.phpt b/ext/session/tests/bug74892.phpt index 916120cb4d44..7a2211210b45 100644 --- a/ext/session/tests/bug74892.phpt +++ b/ext/session/tests/bug74892.phpt @@ -4,6 +4,7 @@ Bug #74892 Url Rewriting (trans_sid) not working on urls that start with # session.use_cookies=0 session.use_only_cookies=0 session.use_trans_sid=1 +session.use_strict_mode=0 --EXTENSIONS-- session --SKIPIF-- diff --git a/ext/session/tests/bug80774.phpt b/ext/session/tests/bug80774.phpt index 7dd86add02b0..443a5d30c964 100644 --- a/ext/session/tests/bug80774.phpt +++ b/ext/session/tests/bug80774.phpt @@ -9,5 +9,5 @@ session_id('12345'); session_start(); ?> --EXPECTHEADERS-- -Set-Cookie: foo\bar=12345; path=/ +Set-Cookie: foo\bar=12345; path=/; HttpOnly; SameSite=Lax --EXPECT-- diff --git a/ext/session/tests/gh9200.phpt b/ext/session/tests/gh9200.phpt index fd6e28ab8b90..7e458176d10f 100644 --- a/ext/session/tests/gh9200.phpt +++ b/ext/session/tests/gh9200.phpt @@ -2,6 +2,7 @@ GH-9200: setcookie has an obsolete expires date format --INI-- session.cookie_lifetime=3600 +session.use_strict_mode=0 --EXTENSIONS-- session --CGI-- @@ -12,7 +13,7 @@ session_id('bar'); session_start(); foreach (headers_list() as $header) { - if (preg_match('/^Set-Cookie: foo=bar; expires=(Mon|Tue|Wed|Thu|Fri|Sat|Sun), [0-9][0-9] (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) 2[0-9][0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] GMT; Max-Age=3600; path=\\/$/', $header)) { + if (preg_match('/^Set-Cookie: foo=bar; expires=(Mon|Tue|Wed|Thu|Fri|Sat|Sun), [0-9][0-9] (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) 2[0-9][0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] GMT; Max-Age=3600; path=\/; HttpOnly; SameSite=Lax$/', $header)) { echo "Success", PHP_EOL; exit; } diff --git a/ext/session/tests/mod_files/gc_dirdepth2.phpt b/ext/session/tests/mod_files/gc_dirdepth2.phpt index a8724285125a..d858c1f95db8 100644 --- a/ext/session/tests/mod_files/gc_dirdepth2.phpt +++ b/ext/session/tests/mod_files/gc_dirdepth2.phpt @@ -7,6 +7,7 @@ session --INI-- session.gc_probability=0 session.gc_maxlifetime=10 +session.use_strict_mode=0 --FILE-- --EXPECTF-- Warning: PHP Startup: session.cookie_samesite must be "Strict", "Lax", "None", or "" in Unknown on line 0 -string(0) "" +string(3) "Lax" Done diff --git a/ext/session/tests/session_start_partitioned_headers.phpt b/ext/session/tests/session_start_partitioned_headers.phpt index 6fa3815aa85d..1f9dd0a7e092 100644 --- a/ext/session/tests/session_start_partitioned_headers.phpt +++ b/ext/session/tests/session_start_partitioned_headers.phpt @@ -9,5 +9,5 @@ session_set_cookie_params(["secure" => true, "partitioned" => true]); session_start(); ?> --EXPECTHEADERS-- -Set-Cookie: PHPSESSID=12345; path=/; secure; Partitioned +Set-Cookie: PHPSESSID=12345; path=/; secure; Partitioned; HttpOnly; SameSite=Lax --EXPECT-- From 23ebec1598bb2421240ed2cd24c6c463f4be3146 Mon Sep 17 00:00:00 2001 From: Jorg Sowa Date: Sun, 17 May 2026 19:28:42 +0200 Subject: [PATCH 3/4] test: adjusted test files --- ext/session/tests/bug80774.phpt | 2 ++ ext/session/tests/mod_files/gc_dirdepth_multi_subdir_count.phpt | 1 + ext/session/tests/session_start_partitioned_headers.phpt | 2 ++ 3 files changed, 5 insertions(+) diff --git a/ext/session/tests/bug80774.phpt b/ext/session/tests/bug80774.phpt index 443a5d30c964..ba5d39e1953a 100644 --- a/ext/session/tests/bug80774.phpt +++ b/ext/session/tests/bug80774.phpt @@ -2,6 +2,8 @@ Bug #80774 (session_name() problem with backslash) --EXTENSIONS-- session +--INI-- +session.use_strict_mode=0 --FILE-- Date: Mon, 18 May 2026 22:04:07 +0200 Subject: [PATCH 4/4] [skip-ci] Update NEWS --- NEWS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 7bae147af6df..e37effff3afe 100644 --- a/NEWS +++ b/NEWS @@ -157,8 +157,7 @@ PHP NEWS . Null bytes in session.cookie_path, session.cookie_domain, and session.cache_limiter are now rejected with a warning. (jorgsowa) . Changed defaults of session.use_strict_mode (now 1), session.cookie_httponly - (now 1) and session.cookie_samesite (now "Lax") to provide secure session - behavior out of the box. (RFC: Secure Session Configuration Defaults) + (now 1) and session.cookie_samesite (now "Lax"). (jorgsowa) - Soap: . Soap::__setCookie() when cookie name is a digit is now not stored and