From 4362b7d9f39189472950589ce47a483b6025f5e9 Mon Sep 17 00:00:00 2001 From: Andrey Andreev Date: Wed, 5 Jan 2022 17:52:40 +0200 Subject: Merge pull request #6025 from gxgpet/develop SameSite attribute implementation for CI_Input::set_cookie --- system/core/Input.php | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) (limited to 'system') diff --git a/system/core/Input.php b/system/core/Input.php index c3a0a9094..7205a7aa2 100644 --- a/system/core/Input.php +++ b/system/core/Input.php @@ -357,14 +357,15 @@ class CI_Input { * @param string $prefix Cookie name prefix * @param bool $secure Whether to only transfer cookies via SSL * @param bool $httponly Whether to only makes the cookie accessible via HTTP (no javascript) + * @param string $samesite SameSite attribute * @return void */ - public function set_cookie($name, $value = '', $expire = '', $domain = '', $path = '/', $prefix = '', $secure = NULL, $httponly = NULL) + public function set_cookie($name, $value = '', $expire = '', $domain = '', $path = '/', $prefix = '', $secure = NULL, $httponly = NULL, $samesite = NULL) { if (is_array($name)) { // always leave 'name' in last place, as the loop will break otherwise, due to $$item - foreach (array('value', 'expire', 'domain', 'path', 'prefix', 'secure', 'httponly', 'name') as $item) + foreach (array('value', 'expire', 'domain', 'path', 'prefix', 'secure', 'httponly', 'name', 'samesite') as $item) { if (isset($name[$item])) { @@ -405,7 +406,42 @@ class CI_Input { $expire = ($expire > 0) ? time() + $expire : 0; } - setcookie($prefix.$name, $value, $expire, $path, $domain, $secure, $httponly); + isset($samesite) OR $samesite = config_item('cookie_samesite'); + if (isset($samesite)) + { + $samesite = ucfirst(strtolower($samesite)); + in_array($samesite, array('Lax', 'Strict', 'None'), TRUE) OR $samesite = 'Lax'; + } + else + { + $samesite = 'Lax'; + } + + if ($samesite === 'None' && ! $secure) + { + 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 + { + $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; + header($cookie_header); + } } // -------------------------------------------------------------------- -- cgit v1.2.3-24-g4f1b From f370f2c042905e521fa3a25283f7e275606a8724 Mon Sep 17 00:00:00 2001 From: Andrey Andreev Date: Wed, 5 Jan 2022 18:23:06 +0200 Subject: Polish changes from PR #6025 --- system/core/Input.php | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) (limited to 'system') diff --git a/system/core/Input.php b/system/core/Input.php index 7205a7aa2..47bdbfc5d 100644 --- a/system/core/Input.php +++ b/system/core/Input.php @@ -422,26 +422,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); } // -------------------------------------------------------------------- -- cgit v1.2.3-24-g4f1b From 1a2651040ef701e750b1c13cd69cc70814b079d0 Mon Sep 17 00:00:00 2001 From: Andrey Andreev Date: Wed, 5 Jan 2022 18:52:24 +0200 Subject: Add SameSite cookie support to Session library --- system/libraries/Session/Session.php | 83 +++++++++++++++++++++++------ system/libraries/Session/Session_driver.php | 24 +++++++-- 2 files changed, 86 insertions(+), 21 deletions(-) (limited to 'system') diff --git a/system/libraries/Session/Session.php b/system/libraries/Session/Session.php index ed379146d..1c5c980ae 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(); @@ -284,13 +305,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'] + ) ); } -- cgit v1.2.3-24-g4f1b