Commit 2da5fed0 authored by Benjamin Franzke's avatar Benjamin Franzke
Browse files

[BUGFIX] Fix HTTP_HOST verification when HTTPS is proxied to HTTP

The HTTP_HOST verification failed if the proxy server port was
different to the local webserver port, due to an assumption in
hostHeaderValueMatchesTrustedHostsPattern() that concluded
that the local webserver port needs to match the default
port of the proxy server.

In case a HTTPS termination proxy is used, that
assumption can not be made, as it is common
practice to use HTTP backends behind a HTTPS
proxy in private networks. Therefore the port
is now verified against the default port of
the current webserver, not a possible proxy server.

Scenario:
 * Proxy Server HTTPS (SSL termination) => Port 443
 * Application Server HTTP => Port 80
 * Default trustedHostsPattern setting

It was previously required to configure a (slow)
trustedHostsPattern to circumvent this issue.

Releases: master, 10.4
Resolves: #94113
Change-Id: I294b87164aee834d8c0b5e0a75da3e19051fe592
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/66613


Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: Jochen's avatarJochen <rothjochen@gmail.com>
Tested-by: Benjamin Franzke's avatarBenjamin Franzke <bfr@qbus.de>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Jochen's avatarJochen <rothjochen@gmail.com>
Reviewed-by: Benjamin Franzke's avatarBenjamin Franzke <bfr@qbus.de>
parent 1afed573
......@@ -2620,10 +2620,7 @@ class GeneralUtility
if (self::cmpIP($_SERVER['REMOTE_ADDR'] ?? '', $proxySSL)) {
$retVal = true;
} else {
// https://secure.php.net/manual/en/reserved.variables.server.php
// "Set to a non-empty value if the script was queried through the HTTPS protocol."
$retVal = !empty($_SERVER['SSL_SESSION_ID'])
|| (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off');
$retVal = self::webserverUsesHttps();
}
break;
case '_ARRAY':
......@@ -2706,15 +2703,21 @@ class GeneralUtility
public static function hostHeaderValueMatchesTrustedHostsPattern($hostHeaderValue)
{
if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === self::ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME) {
// Allow values that equal the server name
// Note that this is only secure if name base virtual host are configured correctly in the webserver
$defaultPort = self::getIndpEnv('TYPO3_SSL') ? '443' : '80';
$parsedHostValue = parse_url('http://' . $hostHeaderValue);
$host = strtolower($hostHeaderValue);
// Default port to be verified if HTTP_HOST does not contain explicit port information.
// Deriving from raw/local webserver HTTPS information (not taking possible proxy configurations into account)
// as we compare against the raw/local server information (SERVER_PORT).
$port = self::webserverUsesHttps() ? '443' : '80';
$parsedHostValue = parse_url('http://' . $host);
if (isset($parsedHostValue['port'])) {
$hostMatch = (strtolower($parsedHostValue['host']) === strtolower($_SERVER['SERVER_NAME']) && (string)$parsedHostValue['port'] === $_SERVER['SERVER_PORT']);
} else {
$hostMatch = (strtolower($hostHeaderValue) === strtolower($_SERVER['SERVER_NAME']) && $defaultPort === $_SERVER['SERVER_PORT']);
$host = $parsedHostValue['host'];
$port = (string)$parsedHostValue['port'];
}
// Allow values that equal the server name
// Note that this is only secure if name base virtual host are configured correctly in the webserver
$hostMatch = $host === strtolower($_SERVER['SERVER_NAME']) && $port === $_SERVER['SERVER_PORT'];
} else {
// In case name based virtual hosts are not possible, we allow setting a trusted host pattern
// See https://typo3.org/teams/security/security-bulletins/typo3-core/typo3-core-sa-2014-001/ for further details
......@@ -2724,6 +2727,27 @@ class GeneralUtility
return $hostMatch;
}
/**
* Determine if the webserver uses HTTPS.
*
* HEADS UP: This does not check if the client performed a
* HTTPS request, as possible proxies are not taken into
* account. It provides raw information about the current
* webservers configuration only.
*
* @return bool
*/
protected static function webserverUsesHttps()
{
if (!empty($_SERVER['SSL_SESSION_ID'])) {
return true;
}
// https://secure.php.net/manual/en/reserved.variables.server.php
// "Set to a non-empty value if the script was queried through the HTTPS protocol."
return !empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off';
}
/**
* Allows internal requests to the install tool and from the command line.
* We accept this risk to have the install tool always available.
......
......@@ -1586,6 +1586,35 @@ class GeneralUtilityTest extends UnitTestCase
self::assertSame($isAllowed, GeneralUtilityFixture::isAllowedHostHeaderValue($httpHost));
}
/**
* @param string $httpHost
* @param string $serverName
* @param bool $isAllowed
* @param string $serverPort
* @param string $ssl
*
* @test
* @dataProvider serverNamePatternDataProvider
*/
public function isAllowedHostHeaderValueWorksCorrectlyWithWithServerNamePatternAndSslProxy($httpHost, $serverName, $isAllowed, $serverPort = '80', $ssl = 'Off')
{
$backup = ['sys' => $GLOBALS['TYPO3_CONF_VARS']['SYS'], 'server' => $_SERVER];
$GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME;
$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxySSL'] = '*';
$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'] = '10.0.0.1';
$_SERVER['REMOTE_ADDR'] = '10.0.0.1';
$_SERVER['SERVER_NAME'] = $serverName;
$_SERVER['SERVER_PORT'] = $serverPort;
$_SERVER['HTTPS'] = $ssl;
self::assertSame($isAllowed, GeneralUtilityFixture::isAllowedHostHeaderValue($httpHost));
$GLOBALS['TYPO3_CONF_VARS']['SYS'] = $backup['sys'];
$_SERVER = $backup['server'];
}
/**
* @test
*/
......
......@@ -79,11 +79,10 @@ class SetupCheck implements CheckInterface
'Trusted hosts pattern is configured to allow current host value.'
));
} else {
$defaultPort = GeneralUtility::getIndpEnv('TYPO3_SSL') ? '443' : '80';
$this->messageQueue->enqueue(new FlashMessage(
'The trusted hosts pattern will be configured to allow all header values. This is because your $SERVER_NAME:[defaultPort]'
. ' is "' . $_SERVER['SERVER_NAME'] . ':' . $defaultPort . '" while your HTTP_HOST:SERVER_PORT is "'
. $_SERVER['HTTP_HOST'] . ':' . $_SERVER['SERVER_PORT'] . '". Check the pattern defined in Admin'
'The trusted hosts pattern will be configured to allow all header values. This is because your $SERVER_NAME:$SERVER_PORT'
. ' is "' . $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT'] . '" while your HTTP_HOST is "'
. $_SERVER['HTTP_HOST'] . '". Check the pattern defined in Admin'
. ' Tools -> Settings -> Configure Installation-Wide Options -> System -> trustedHostsPattern'
. ' and adapt it to expected host value(s).',
'Trusted hosts pattern mismatch',
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment