Skip to content

Commit

Permalink
[FEATURE] Implement SameSite option for TYPO3 cookies
Browse files Browse the repository at this point in the history
This change introduces a new security option for setting the SameSite
option to all cookies sent by TYPO3 Core.

Namely:
- Frontend User Sessions ("lax" by default)
- Backend User Sessions ("strict" by default)
- Install Tool Sessions ("strict", none-configurable)
- Last Login Provider in Backend ("strict", non-configurable)

This means that these can only be accessed by scripts and requests
by the same site, and not by any third-party scripts.

Since we're talking about actual cookies for a user, and not
ads-related or third-party login-dependant cookies, the default
options fit just perfectly.

All modern browsers except Internet Explorer respect this option
to be set. Please note that Firefox and Chrome will have "SameSite=lax"
set in Q1/2020 by default if NO SameSite option is set at all. This change
allows to configure this.

Backend and Frontend User Cookies can be configured to "strict", "lax"
or "none" (= same as before), whereas "none" only works for secure
connections (= HTTPS).

If "strict" is in place, security via CSRF is not needed anymore, and can
be dropped in the future.

Resolves: #90351
Releases: master, 9.5, 8.7
Change-Id: I8095e2a552faa9d1fd4fa7855297302a9ec6a75f
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/63183
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
  • Loading branch information
bmack authored and georgringer committed Feb 13, 2020
1 parent ac17522 commit de29dc2
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 59 deletions.
1 change: 1 addition & 0 deletions composer.json
Expand Up @@ -58,6 +58,7 @@
"symfony/dependency-injection": "^4.4 || ^5.0",
"symfony/expression-language": "^4.4 || ^5.0",
"symfony/finder": "^4.4 || ^5.0",
"symfony/http-foundation": "^4.4 || ^5.0",
"symfony/mailer": "^4.4 || ^5.0",
"symfony/mime": "^4.4 || ^5.0",
"symfony/polyfill-intl-icu": "^1.6",
Expand Down
112 changes: 56 additions & 56 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 17 additions & 2 deletions typo3/sysext/backend/Classes/Controller/LoginController.php
Expand Up @@ -20,6 +20,7 @@
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Component\HttpFoundation\Cookie;
use TYPO3\CMS\Backend\LoginProvider\Event\ModifyPageLayoutOnLoginProviderSelectionEvent;
use TYPO3\CMS\Backend\LoginProvider\LoginProviderInterface;
use TYPO3\CMS\Backend\Routing\UriBuilder;
Expand All @@ -30,6 +31,7 @@
use TYPO3\CMS\Core\FormProtection\BackendFormProtection;
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
use TYPO3\CMS\Core\Http\HtmlResponse;
use TYPO3\CMS\Core\Http\NormalizedParams;
use TYPO3\CMS\Core\Information\Typo3Information;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Localization\Locales;
Expand Down Expand Up @@ -565,11 +567,24 @@ protected function detectLoginProvider(ServerRequestInterface $request): string
reset($this->loginProviders);
$loginProvider = key($this->loginProviders);
}
// Use the secure option when the current request is served by a secure connection:
// Use the secure option when the current request is served by a secure connection
/** @var NormalizedParams $normalizedParams */
$normalizedParams = $request->getAttribute('normalizedParams');
$isHttps = $normalizedParams->isHttps();
$cookieSecure = (bool)$GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieSecure'] && $isHttps;
setcookie('be_lastLoginProvider', (string)$loginProvider, $GLOBALS['EXEC_TIME'] + 7776000, '', '', $cookieSecure, true); // 90 days
$cookie = new Cookie(
'be_lastLoginProvider',
(string)$loginProvider,
$GLOBALS['EXEC_TIME'] + 7776000, // 90 days
$normalizedParams->getSitePath() . TYPO3_mainDir,
'',
$cookieSecure,
true,
false,
Cookie::SAMESITE_STRICT
);
header('Set-Cookie: ' . $cookie->__toString(), false);

return (string)$loginProvider;
}

Expand Down
Expand Up @@ -16,6 +16,7 @@

use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Component\HttpFoundation\Cookie;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Crypto\Random;
use TYPO3\CMS\Core\Database\Connection;
Expand Down Expand Up @@ -451,9 +452,25 @@ protected function setSessionCookie()
$cookieExpire = $isRefreshTimeBasedCookie ? $GLOBALS['EXEC_TIME'] + $this->lifetime : 0;
// Use the secure option when the current request is served by a secure connection:
$cookieSecure = (bool)$settings['cookieSecure'] && GeneralUtility::getIndpEnv('TYPO3_SSL');
$cookieSameSite = $this->getCookieSameSite();
// None needs the secure option (only allowed on HTTPS)
if ($cookieSameSite === Cookie::SAMESITE_NONE) {
$cookieSecure = true;
}
// Do not set cookie if cookieSecure is set to "1" (force HTTPS) and no secure channel is used:
if ((int)$settings['cookieSecure'] !== 1 || GeneralUtility::getIndpEnv('TYPO3_SSL')) {
setcookie($this->name, $this->id, $cookieExpire, $cookiePath, $cookieDomain, $cookieSecure, true);
$cookie = new Cookie(
$this->name,
$this->id,
$cookieExpire,
$cookiePath,
$cookieDomain,
$cookieSecure,
true,
false,
$cookieSameSite
);
header('Set-Cookie: ' . $cookie->__toString(), false);
$this->cookieWasSetOnCurrentRequest = true;
} else {
throw new Exception('Cookie was not set since HTTPS was forced in $TYPO3_CONF_VARS[SYS][cookieSecure].', 1254325546);
Expand All @@ -465,6 +482,24 @@ protected function setSessionCookie()
}
}

/**
* Fetches the cookie information from the current LocalConfiguration option, based on the $loginType
* which is either "BE" or "FE".
* Valid options are "strict", "lax" or "none", whereas "none" only works in HTTPS requests.
*
* If nothing is defined, or a wrong value is defined, a fallback to "strict" is put in place.
*
* @return string
*/
protected function getCookieSameSite(): string
{
$cookieSameSite = strtolower($GLOBALS['TYPO3_CONF_VARS'][$this->loginType]['cookieSameSite'] ?? Cookie::SAMESITE_STRICT);
if (!in_array($cookieSameSite, [Cookie::SAMESITE_STRICT, Cookie::SAMESITE_LAX, Cookie::SAMESITE_NONE], true)) {
$cookieSameSite = Cookie::SAMESITE_STRICT;
}
return $cookieSameSite;
}

/**
* Gets the domain to be used on setting cookies.
* The information is taken from the value in $GLOBALS['TYPO3_CONF_VARS']['SYS']['cookieDomain'].
Expand Down
2 changes: 2 additions & 0 deletions typo3/sysext/core/Configuration/DefaultConfiguration.php
Expand Up @@ -1082,6 +1082,7 @@
'enabledBeUserIPLock' => true,
'cookieDomain' => '',
'cookieName' => 'be_typo_user',
'cookieSameSite' => 'strict',
'loginSecurityLevel' => 'normal',
'showRefreshLoginPopup' => false,
'adminOnly' => 0,
Expand Down Expand Up @@ -1262,6 +1263,7 @@
'permalogin' => 0,
'cookieDomain' => '',
'cookieName' => 'fe_typo_user',
'cookieSameSite' => 'lax',
'defaultUserTSconfig' => '',
'defaultTypoScript_constants' => '',
'defaultTypoScript_constants.' => [], // Lines of TS to include after a static template with the uid = the index in the array (Constants)
Expand Down
Expand Up @@ -314,6 +314,13 @@ BE:
cookieName:
type: text
description: 'Set the name for the cookie used for the back-end user session'
cookieSameSite:
type: text
allowedValues:
'lax': 'Cookies set by TYPO3 are only available for the current site, third-party integrations are not allowed to read cookies, except for links and simple HTML forms'
'strict': 'Cookies sent by TYPO3 are only available for the current site, never shared to other third-party packages'
'none': 'Allow cookies set by TYPO3 to be sent to other sites as well, please note - this only works with HTTPS connections'
description: 'Indicates that the cookie should send proper information where the cookie can be shared (first-party cookies vs. third-party cookies) in TYPO3 Backend.'
loginSecurityLevel:
type: text
description: 'Keywords that determines the security level of login to the backend. "normal" means the password from the login form is sent in clear-text. The client/server communication should be secured with HTTPS.'
Expand Down Expand Up @@ -447,6 +454,13 @@ FE:
cookieName:
type: text
description: 'Set the name for the cookie used for the front-end user session'
cookieSameSite:
type: text
allowedValues:
'lax': 'Cookies set by TYPO3 are only available for the current site, third-party integrations are not allowed to read cookies, except for links and simple HTML forms'
'strict': 'Cookies sent by TYPO3 are only available for the current site, never shared to other third-party packages'
'none': 'Allow cookies set by TYPO3 to be sent to other sites as well, please note - this only works with HTTPS connections'
description: 'Indicates that the cookie should send proper information where the cookie can be shared (first-party cookies vs. third-party cookies) in TYPO3 Frontend.'
defaultUserTSconfig:
type: multiline
description: 'Enter lines of default frontend user/group TSconfig.'
Expand Down
@@ -0,0 +1,60 @@
.. include:: ../../Includes.txt

====================================================================
Feature: #90351 - Configure TYPO3-shipped cookies with SameSite flag
====================================================================

See :issue:`90351`

Description
===========

TYPO3 Core sends four cookies set by PHP to the browser when a session is requested:

- fe_typo_user - used to identify a session ID when logged-in to the TYPO3 Frontend
- be_typo_user - used to identify a backend session when a Backend User logged in to TYPO3 Backend or Frontend
- Typo3InstallTool - used to validate a session for the System Maintenance Area / "Install Tool"
- be_lastLoginProvider - stores information about the last login provider when logging into TYPO3 Backend

All modern wide-spread browsers (Mozilla Firefox, Chromium-based Browsers such as Google Chrome, Safari, Microsoft Edge) support sending cookies with an additional flag called "SameSite" which
defines the visibility of a cookie when used in other scripts or
iframes such as a YouTube video embedded into a site. The same site
flag defines whether to send such information to these "third-party
sites".

Starting with Google Chrome 80 (expected in February 2020), the browser treats any cookie without having the SameSite flag sent to
be the same as "lax".

TYPO3 now supports the configuration of this cookie for Frontend-
and Backend users. For the install Tool and lastLoginProvider
the cookies are now always sent with the "strict" flag set.

SameSite enhances privacy for every visitor or editor of your
TYPO3 installation.

Read more about SameSite cookies on: https://web.dev/samesite-cookies-explained/


Impact
======

All cookies sent by TYPO3 Core now send the SameSite flag by default, whereas TYPO3 Frontend sends the SameSite flag "lax",
and all other cookies are sent via "strict".

The cookies for Frontend User Sessions can be configured via
`$GLOBALS[TYPO3_CONF_VARS][FE][cookieSameSite]` to be either
"strict", "lax" or "none".

The cookies for Backend User Sessions can be configured via
`$GLOBALS[TYPO3_CONF_VARS][BE][cookieSameSite]` to be either
"strict", "lax" or "none".

Please note that "none" only works when running the site via HTTPS.

Older browsers without SameSite support do not consider evaluating
the SameSite flag will behave as before.

Both settings can be configured in the Install Tool / Maintenance
Area Settings module.

.. index:: LocalConfiguration, ext:core
1 change: 1 addition & 0 deletions typo3/sysext/core/composer.json
Expand Up @@ -39,6 +39,7 @@
"symfony/dependency-injection": "^4.4 || ^5.0",
"symfony/expression-language": "^4.4 || ^5.0",
"symfony/finder": "^4.4 || ^5.0",
"symfony/http-foundation": "^4.4 || ^5.0",
"symfony/mailer": "^4.4 || ^5.0",
"symfony/mime": "^4.4 || ^5.0",
"symfony/polyfill-intl-icu": "^1.6",
Expand Down

0 comments on commit de29dc2

Please sign in to comment.