diff options
-rw-r--r-- | application/config/config.php | 5 | ||||
-rwxr-xr-x | build-release.sh | 3 | ||||
-rw-r--r-- | system/core/Input.php | 37 | ||||
-rw-r--r-- | system/libraries/Session/Session.php | 83 | ||||
-rw-r--r-- | system/libraries/Session/Session_driver.php | 24 | ||||
-rw-r--r-- | user_guide_src/source/changelog.rst | 2 | ||||
-rw-r--r-- | user_guide_src/source/libraries/sessions.rst | 2 |
7 files changed, 117 insertions, 39 deletions
diff --git a/application/config/config.php b/application/config/config.php index 596f8fefe..d66d4fce4 100644 --- a/application/config/config.php +++ b/application/config/config.php @@ -329,6 +329,10 @@ $config['encryption_key'] = ''; | | The session cookie name, must contain only [0-9a-z_-] characters | +| 'sess_samesite' +| +| Session cookie SameSite attribute: Lax (default), Strict or None +| | 'sess_expiration' | | The number of SECONDS you want the session to last. @@ -369,6 +373,7 @@ $config['encryption_key'] = ''; */ $config['sess_driver'] = 'files'; $config['sess_cookie_name'] = 'ci_session'; +$config['sess_samesite'] = 'Lax'; $config['sess_expiration'] = 7200; $config['sess_save_path'] = NULL; $config['sess_match_ip'] = FALSE; diff --git a/build-release.sh b/build-release.sh index 6b3b31d12..30967fc2e 100755 --- a/build-release.sh +++ b/build-release.sh @@ -47,7 +47,7 @@ fi echo "Running tests ..." -cd tests/ +php -d zend.enable_gc=0 -d date.timezone=UTC -d mbstring.func_overload=7 -d mbstring.internal_encoding=UTF-8 vendor/bin/phpunit --coverage-text --configuration tests/travis/sqlite.phpunit.xml phpunit if [ $? -ne 0 ] @@ -56,7 +56,6 @@ then exit 1 fi -cd .. cd user_guide_src/ echo "" diff --git a/system/core/Input.php b/system/core/Input.php index 71f28221f..0beb252ed 100644 --- a/system/core/Input.php +++ b/system/core/Input.php @@ -365,26 +365,31 @@ class CI_Input { log_message('error', $name.' cookie sent with SameSite=None, but without Secure attribute.'); } - if (is_php('7.3')) - { - $setcookie_options = array( - 'expires' => $expire, - 'path' => $path, - 'domain' => $domain, - 'secure' => $secure, - 'httponly' => $httponly, - 'samesite' => $samesite, - ); - setcookie($prefix.$name, $value, $setcookie_options); - } - else + if ( ! is_php('7.3')) { + $maxage = $expire - time(); + if ($maxage < 1) + { + $maxage = 0; + } + $cookie_header = 'Set-Cookie: '.$prefix.$name.'='.rawurlencode($value); - $cookie_header .= ($expire === 0 ? '' : '; expires='.gmdate('D, d-M-Y H:i:s T', $expire)); - $cookie_header .= '; path='.$path.($domain !== '' ? '; domain='.$domain : ''); - $cookie_header .= ($secure ? '; secure' : '').($httponly ? '; HttpOnly' : '').'; SameSite='.$samesite; + $cookie_header .= ($expire === 0 ? '' : '; Expires='.gmdate('D, d-M-Y H:i:s T', $expire)).'; Max-Age='.$maxage; + $cookie_header .= '; Path='.$path.($domain !== '' ? '; Domain='.$domain : ''); + $cookie_header .= ($secure ? '; Secure' : '').($httponly ? '; HttpOnly' : '').'; SameSite='.$samesite; header($cookie_header); + return; } + + $setcookie_options = array( + 'expires' => $expire, + 'path' => $path, + 'domain' => $domain, + 'secure' => $secure, + 'httponly' => $httponly, + 'samesite' => $samesite, + ); + setcookie($prefix.$name, $value, $setcookie_options); } // -------------------------------------------------------------------- diff --git a/system/libraries/Session/Session.php b/system/libraries/Session/Session.php index 6479d7242..f370f7f19 100644 --- a/system/libraries/Session/Session.php +++ b/system/libraries/Session/Session.php @@ -154,15 +154,36 @@ class CI_Session { // unless it is being currently created or regenerated elseif (isset($_COOKIE[$this->_config['cookie_name']]) && $_COOKIE[$this->_config['cookie_name']] === session_id()) { - setcookie( - $this->_config['cookie_name'], - session_id(), - (empty($this->_config['cookie_lifetime']) ? 0 : time() + $this->_config['cookie_lifetime']), - $this->_config['cookie_path'], - $this->_config['cookie_domain'], - $this->_config['cookie_secure'], - TRUE - ); + $expires = empty($this->_config['cookie_lifetime']) ? 0 : time() + $this->_config['cookie_lifetime']; + if (is_php('7.3')) + { + setcookie( + $this->_config['cookie_name'], + session_id(), + array( + 'expires' => $expires, + 'path' => $this->_config['cookie_path'], + 'domain' => $this->_config['cookie_domain'], + 'secure' => $this->_config['cookie_secure'], + 'httponly' => TRUE, + 'samesite' => $this->_config['cookie_samesite'] + ) + ); + } + else + { + $header = 'Set-Cookie: '.$this->_config['cookie_name'].'='.session_id(); + $header .= empty($expires) ? '' : '; Expires='.gmdate('D, d-M-Y H:i:s T', $expires).'; Max-Age='.$this->_config['cookie_lifetime']; + $header .= '; Path='.$this->_config['cookie_path']; + $header .= ($this->_config['cookie_domain'] !== '' ? '; Domain='.$this->_config['cookie_domain'] : ''); + $header .= ($this->_config['cookie_secure'] ? '; Secure' : '').'; HttpOnly; SameSite='.$this->_config['cookie_samesite']; + header($header); + } + + if ( ! $this->_config['cookie_secure'] && $this->_config['cookie_samesite'] === 'None') + { + log_message('error', 'Session:', $this->_config['cookie_name'].' cookie sent with SameSite=None, but without Secure attribute.'); + } } $this->_ci_init_vars(); @@ -281,13 +302,43 @@ class CI_Session { isset($params['cookie_domain']) OR $params['cookie_domain'] = config_item('cookie_domain'); isset($params['cookie_secure']) OR $params['cookie_secure'] = (bool) config_item('cookie_secure'); - session_set_cookie_params( - $params['cookie_lifetime'], - $params['cookie_path'], - $params['cookie_domain'], - $params['cookie_secure'], - TRUE // HttpOnly; Yes, this is intentional and not configurable for security reasons - ); + isset($params['cookie_samesite']) OR $params['cookie_samesite'] = config_item('sess_samesite'); + if ( ! isset($params['cookie_samesite']) && is_php('7.3')) + { + $params['cookie_samesite'] = ini_get('session.cookie_samesite'); + } + + if (isset($params['cookie_samesite'])) + { + $params['cookie_samesite'] = ucfirst(strtolower($params['cookie_samesite'])); + in_array($params['cookie_samesite'], array('Lax', 'Strict', 'None'), TRUE) OR $params['cookie_samesite'] = 'Lax'; + } + else + { + $params['cookie_samesite'] = 'Lax'; + } + + if (is_php('7.3')) + { + session_set_cookie_params(array( + 'lifetime' => $params['cookie_lifetime'], + 'path' => $params['cookie_path'], + 'domain' => $params['cookie_domain'], + 'secure' => $params['cookie_secure'], + 'httponly' => $params['cookie_httponly'], + 'samesite' => $params['cookie_samesite'] + )); + } + else + { + session_set_cookie_params( + $params['cookie_lifetime'], + $params['cookie_path'], + $params['cookie_domain'], + $params['cookie_secure'], + TRUE // HttpOnly; Yes, this is intentional and not configurable for security reasons + ); + } if (empty($expiration)) { diff --git a/system/libraries/Session/Session_driver.php b/system/libraries/Session/Session_driver.php index d78492b5e..b1b1b073e 100644 --- a/system/libraries/Session/Session_driver.php +++ b/system/libraries/Session/Session_driver.php @@ -140,14 +140,28 @@ abstract class CI_Session_driver { */ protected function _cookie_destroy() { + if ( ! is_php('7.3')) + { + $header = 'Set-Cookie: '.$this->_config['cookie_name'].'='; + $header .= '; Expires='.gmdate('D, d-M-Y H:i:s T', 1).'; Max-Age=-1'; + $header .= '; Path='.$this->_config['cookie_path']; + $header .= ($this->_config['cookie_domain'] !== '' ? '; Domain='.$this->_config['cookie_domain'] : ''); + $header .= ($this->_config['cookie_secure'] ? '; Secure' : '').'; HttpOnly; SameSite='.$this->_config['cookie_samesite']; + header($header); + return; + } + return setcookie( $this->_config['cookie_name'], NULL, - 1, - $this->_config['cookie_path'], - $this->_config['cookie_domain'], - $this->_config['cookie_secure'], - TRUE + array( + 'expires' => 1, + 'path' => $this->_config['cookie_path'], + 'domain' => $this->_config['cookie_domain'], + 'secure' => $this->_config['cookie_secure'], + 'httponly' => TRUE, + 'samesite' => $this->_config['cookie_samesite'] + ) ); } diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst index 7bd51c81e..b4278b1cd 100644 --- a/user_guide_src/source/changelog.rst +++ b/user_guide_src/source/changelog.rst @@ -145,6 +145,8 @@ Release Date: Not Released - Added method :doc:`Database Library <database/index>` method ``trans_active()`` to expose transaction state. - Updated :doc:`Database Library <database/index>` 'pdo' driver to attempt to free resources in order to allow connections to be closed. - Added ``SameSite=Strict`` attribute to the CSRF cookie sent by the :doc:`Security Class <libraries/security>`. + - Added ``$config['cookie_samesite']`` option and ``$samesite`` parameter to :doc:`Input Library <libraries/input>` method ``set_cookie()``. + - Added ``SameSite`` support through ``$config['sess_samesite']`` option to the :doc:`Session Library <libraries/sessions>`. - Added a wrapper class around :doc:`Session <libraries/sessions>` drivers to deal with compatibility between PHP 8.1 and older versions. - Updated a lot of code for PHP 8.0 and 8.1 compatibility. diff --git a/user_guide_src/source/libraries/sessions.rst b/user_guide_src/source/libraries/sessions.rst index f2236c633..b87508aba 100644 --- a/user_guide_src/source/libraries/sessions.rst +++ b/user_guide_src/source/libraries/sessions.rst @@ -438,6 +438,8 @@ Preference Default Options ============================ =============== ======================================== ============================================================================================ **sess_driver** files files/database/redis/memcached/*custom* The session storage driver to use. **sess_cookie_name** ci_session [A-Za-z\_-] characters only The name used for the session cookie. +**sess_samesite** ci_session 'Lax', 'Strict' or 'None' SameSite attribute value for session cookies. + Defaults to ``session.cookie_samesite`` on PHP 7.3+ or 'Lax' if not present at all. **sess_expiration** 7200 (2 hours) Time in seconds (integer) The number of seconds you would like the session to last. If you would like a non-expiring session (until browser is closed) set the value to zero: 0 **sess_save_path** NULL None Specifies the storage location, depends on the driver being used. |