From 1ed6ec1e5cdcd0bacbda67a129efa1aa88648d66 Mon Sep 17 00:00:00 2001 From: rhertogh Date: Mon, 10 Jun 2019 23:33:36 +0200 Subject: [PATCH] Fixes #17353: Added `sameSite` support for `yii\web\Cookie` and `yii\web\Session::cookieParams` --- docs/guide/runtime-sessions-cookies.md | 36 ++++++++++++++++++++++---- docs/guide/security-best-practices.md | 9 +++++++ framework/CHANGELOG.md | 1 + framework/web/Cookie.php | 30 +++++++++++++++++++++ framework/web/Response.php | 16 +++++++++++- framework/web/Session.php | 20 +++++++++++++- 6 files changed, 105 insertions(+), 7 deletions(-) diff --git a/docs/guide/runtime-sessions-cookies.md b/docs/guide/runtime-sessions-cookies.md index b807799831..6ab4ae8492 100644 --- a/docs/guide/runtime-sessions-cookies.md +++ b/docs/guide/runtime-sessions-cookies.md @@ -332,11 +332,6 @@ examples, the [[yii\web\Cookie]] class also defines other properties to fully re information, such as [[yii\web\Cookie::domain|domain]], [[yii\web\Cookie::expire|expire]]. You may configure these properties as needed to prepare a cookie and then add it to the response's cookie collection. -> Note: For better security, the default value of [[yii\web\Cookie::httpOnly]] is set to `true`. This helps mitigate -the risk of a client-side script accessing the protected cookie (if the browser supports it). You may read -the [httpOnly wiki article](https://www.owasp.org/index.php/HttpOnly) for more details. - - ### Cookie Validation When you are reading and sending cookies through the `request` and `response` components as shown in the last @@ -369,3 +364,34 @@ return [ > Info: [[yii\web\Request::cookieValidationKey|cookieValidationKey]] is critical to your application's security. It should only be known to people you trust. Do not store it in the version control system. + +## Security settings + +Both [[yii\web\Cookie]] and [[yii\web\Session]] support the following security flags: + +### httpOnly + +For better security, the default value of [[yii\web\Cookie::httpOnly]] and the 'httponly' parameter of +[[yii\web\Session::cookieParams]] is set to `true`. This helps mitigate the risk of a client-side script accessing +the protected cookie (if the browser supports it). +You may read the [HttpOnly wiki article](https://www.owasp.org/index.php/HttpOnly) for more details. + +### secure +The purpose of the secure flag is to prevent cookies from being send in clear text. If the browser supports the +secure flag it will only include the cookie when the request is sent over a secure (TLS) connection. +You may read the [SecureFlag wiki article](https://www.owasp.org/index.php/SecureFlag) for more details. + +### sameSite +Starting with Yii 2.0.21 the [[yii\web\Cookie::sameSite]] setting is supported. It requires PHP version 7.3.0 or higher. +The purpose of the `sameSite` setting is to prevent CSRF (Cross-Site Request Forgery) attacks. +If the browser supports the `sameSite` setting it will only include the cookie according to the specified policy ('Lax' or 'Strict'). +You may read the [SameSite wiki article](https://www.owasp.org/index.php/SameSite) for more details. +For better security, an exception will be thrown if `sameSite` is used with an unsupported version of PHP. +To use this feature across different PHP versions check the version first. E.g. +```php +[ + 'sameSite' => PHP_VERSION_ID >= 70300 ? yii\web\Cookie::SAME_SITE_LAX : null, +] +``` +> Note: Since not all browsers support the `sameSite` setting yet, it is still strongly recommended to also include + [additional CSRF protection](security-best-practices.md#avoiding-csrf). diff --git a/docs/guide/security-best-practices.md b/docs/guide/security-best-practices.md index 6e12299707..b9e1d01d04 100644 --- a/docs/guide/security-best-practices.md +++ b/docs/guide/security-best-practices.md @@ -253,9 +253,14 @@ class ContactAction extends Action > Warning: Disabling CSRF will allow any site to send POST requests to your site. It is important to implement extra validation such as checking an IP address or a secret token in this case. +> Note: Since version 2.0.21 Yii supports the `sameSite` cookie setting (requires PHP version 7.3.0 or higher). + Setting the `sameSite` cookie setting does not make the above obsolete since not all browsers support the setting yet. + See the [Sessions and Cookies sameSite option](runtime-sessions-cookies.md#samesite) for more information. + Further reading on the topic: - +- Avoiding file exposure @@ -301,6 +306,10 @@ provided by the H5BP project: - [IIS](https://github.com/h5bp/server-configs-iis). - [Lighttpd](https://github.com/h5bp/server-configs-lighttpd). +> Note: When TLS is configured it is recommended that (session) cookies are send over TLS exclusively. + This is achieved by setting the `secure` flag for sessions and/or cookies. + See the [Sessions and Cookies secure flag](runtime-sessions-cookies.md#secure) for more information. + Secure Server configuration --------------------------- diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 99cdb9cc53..d5b5510bf8 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -7,6 +7,7 @@ Yii Framework 2 Change Log - Bug #17341: Fixed error from yii.activeForm.js in strict mode (mikehaertl) - Enh #17345: Improved performance of `yii\db\Connection::quoteColumnName()` (brandonkelly) - Enh #17348: Improved performance of `yii\db\Connection::quoteTableName()` (brandonkelly) +- Enh #17353: Added `sameSite` support for `yii\web\Cookie` and `yii\web\Session::cookieParams` (rhertogh) 2.0.20 June 04, 2019 diff --git a/framework/web/Cookie.php b/framework/web/Cookie.php index 931a1ede1c..788db9e4aa 100644 --- a/framework/web/Cookie.php +++ b/framework/web/Cookie.php @@ -17,6 +17,23 @@ namespace yii\web; */ class Cookie extends \yii\base\BaseObject { + /** + * SameSite policy Lax will prevent the cookie from being sent by the browser in all cross-site browsing context + * during CSRF-prone request methods (e.g. POST, PUT, PATCH etc). + * E.g. a POST request from https://otherdomain.com to https://yourdomain.com will not include the cookie, however a GET request will. + * When a user follows a link from https://otherdomain.com to https://yourdomain.com it will include the cookie + * @see $sameSite + */ + const SAME_SITE_LAX = 'Lax'; + /** + * SameSite policy Strict will prevent the cookie from being sent by the browser in all cross-site browsing context + * regardless of the request method and even when following a regular link. + * E.g. a GET request from https://otherdomain.com to https://yourdomain.com or a user following a link from + * https://otherdomain.com to https://yourdomain.com will not include the cookie. + * @see $sameSite + */ + const SAME_SITE_STRICT = 'Strict'; + /** * @var string name of the cookie */ @@ -48,6 +65,19 @@ class Cookie extends \yii\base\BaseObject * such as JavaScript, which can effectively help to reduce identity theft through XSS attacks. */ public $httpOnly = true; + /** + * @var string SameSite prevents the browser from sending this cookie along with cross-site requests. + * Please note that this feature is only supported since PHP 7.3.0 + * For better security, an exception will be thrown if `sameSite` is set while using an unsupported version of PHP. + * To use this feature across different PHP versions check the version first. E.g. + * ```php + * $cookie->sameSite = PHP_VERSION_ID >= 70300 ? yii\web\Cookie::SAME_SITE_LAX : null, + * ``` + * See https://www.owasp.org/index.php/SameSite for more information about sameSite. + * + * @since 2.0.21 + */ + public $sameSite; /** diff --git a/framework/web/Response.php b/framework/web/Response.php index 22a617cf07..61571476a4 100644 --- a/framework/web/Response.php +++ b/framework/web/Response.php @@ -401,7 +401,21 @@ class Response extends \yii\base\Response if ($cookie->expire != 1 && isset($validationKey)) { $value = Yii::$app->getSecurity()->hashData(serialize([$cookie->name, $value]), $validationKey); } - setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly); + if (PHP_VERSION_ID >= 70300) { + setcookie($cookie->name, $value, [ + 'expires' => $cookie->expire, + 'path' => $cookie->path, + 'domain' => $cookie->domain, + 'secure' => $cookie->secure, + 'httpOnly' => $cookie->httpOnly, + 'sameSite' => !empty($cookie->sameSite) ? $cookie->sameSite : null, + ]); + } else { + if (!is_null($cookie->sameSite)) { + throw new InvalidConfigException(get_class($cookie) . '::sameSite is not supported by PHP versions < 7.3.0 (set it to null in this environment)'); + } + setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly); + } } } diff --git a/framework/web/Session.php b/framework/web/Session.php index 59aeade01d..0e47559146 100644 --- a/framework/web/Session.php +++ b/framework/web/Session.php @@ -367,6 +367,16 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co * The cookie parameters passed to this method will be merged with the result * of `session_get_cookie_params()`. * @param array $value cookie parameters, valid keys include: `lifetime`, `path`, `domain`, `secure` and `httponly`. + * Starting with Yii 2.0.21 `sameSite` is also supported. It requires PHP version 7.3.0 or higher. + * For securtiy, an exception will be thrown if `sameSite` is set while using an unsupported version of PHP. + * To use this feature across different PHP versions check the version first. E.g. + * ```php + * [ + * 'sameSite' => PHP_VERSION_ID >= 70300 ? yii\web\Cookie::SAME_SITE_LAX : null, + * ] + * ``` + * See https://www.owasp.org/index.php/SameSite for more information about `sameSite`. + * * @throws InvalidArgumentException if the parameters are incomplete. * @see https://secure.php.net/manual/en/function.session-set-cookie-params.php */ @@ -385,7 +395,15 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co { $data = $this->getCookieParams(); if (isset($data['lifetime'], $data['path'], $data['domain'], $data['secure'], $data['httponly'])) { - session_set_cookie_params($data['lifetime'], $data['path'], $data['domain'], $data['secure'], $data['httponly']); + if (PHP_VERSION_ID >= 70300) { + session_set_cookie_params($data); + } else { + if (!empty($data['sameSite'])) { + throw new InvalidConfigException('sameSite cookie is not supported by PHP versions < 7.3.0 (set it to null in this environment)'); + } + session_set_cookie_params($data['lifetime'], $data['path'], $data['domain'], $data['secure'], $data['httponly']); + } + } else { throw new InvalidArgumentException('Please make sure cookieParams contains these elements: lifetime, path, domain, secure and httponly.'); }