274 changes: 274 additions & 0 deletions typo3/sysext/core/Tests/Unit/Middleware/VerifyHostHeaderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
<?php

declare(strict_types=1);

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

namespace TYPO3\CMS\Core\Tests\Unit\Middleware;

use Prophecy\PhpUnit\ProphecyTrait;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Middleware\VerifyHostHeader;
use TYPO3\TestingFramework\Core\Unit\UnitTestCase;

class VerifyHostHeaderTest extends UnitTestCase
{
use ProphecyTrait;

/**
* @test
*/
public function isAllowedHostHeaderValueReturnsFalseIfTrustedHostsIsNotConfigured(): void
{
$subject = new VerifyHostHeader('');
$serverParams = $_SERVER;
self::assertFalse($subject->isAllowedHostHeaderValue('evil.foo.bar', $serverParams));
}

public static function hostnamesMatchingTrustedHostsConfigurationDataProvider(): array
{
return [
'hostname without port matching' => ['lolli.did.this', '.*\.did\.this'],
'other hostname without port matching' => ['helmut.did.this', '.*\.did\.this'],
'two different hostnames without port matching 1st host' => ['helmut.is.secure', '(helmut\.is\.secure|lolli\.is\.secure)'],
'two different hostnames without port matching 2nd host' => ['lolli.is.secure', '(helmut\.is\.secure|lolli\.is\.secure)'],
'hostname with port matching' => ['lolli.did.this:42', '.*\.did\.this:42'],
'hostnames are case insensitive 1' => ['lolli.DID.this:42', '.*\.did.this:42'],
'hostnames are case insensitive 2' => ['lolli.did.this:42', '.*\.DID.this:42'],
];
}

public static function hostnamesNotMatchingTrustedHostsConfigurationDataProvider(): array
{
return [
'hostname without port' => ['lolli.did.this', 'helmut\.did\.this'],
'hostname with port, but port not allowed' => ['lolli.did.this:42', 'helmut\.did\.this'],
'two different hostnames in pattern but host header starts with different value #1' => ['sub.helmut.is.secure', '(helmut\.is\.secure|lolli\.is\.secure)'],
'two different hostnames in pattern but host header starts with different value #2' => ['sub.lolli.is.secure', '(helmut\.is\.secure|lolli\.is\.secure)'],
'two different hostnames in pattern but host header ends with different value #1' => ['helmut.is.secure.tld', '(helmut\.is\.secure|lolli\.is\.secure)'],
'two different hostnames in pattern but host header ends with different value #2' => ['lolli.is.secure.tld', '(helmut\.is\.secure|lolli\.is\.secure)'],
];
}

/**
* @param string $httpHost HTTP_HOST string
* @param string $hostNamePattern trusted hosts pattern
* @test
* @dataProvider hostnamesMatchingTrustedHostsConfigurationDataProvider
*/
public function isAllowedHostHeaderValueReturnsTrueIfHostValueMatches(string $httpHost, string $hostNamePattern): void
{
$serverParams = $_SERVER;

$subject = new VerifyHostHeader($hostNamePattern);
self::assertTrue($subject->isAllowedHostHeaderValue($httpHost, $serverParams));
}

/**
* @param string $httpHost HTTP_HOST string
* @param string $hostNamePattern trusted hosts pattern
* @test
* @dataProvider hostnamesNotMatchingTrustedHostsConfigurationDataProvider
*/
public function isAllowedHostHeaderValueReturnsFalseIfHostValueMatches(string $httpHost, string $hostNamePattern): void
{
$serverParams = $_SERVER;

$subject = new VerifyHostHeader($hostNamePattern);
self::assertFalse($subject->isAllowedHostHeaderValue($httpHost, $serverParams));
}

public function serverNamePatternDataProvider(): array
{
return [
'host value matches server name and server port is default http' => [
'httpHost' => 'secure.web.server',
'serverName' => 'secure.web.server',
'isAllowed' => true,
'serverPort' => '80',
'ssl' => 'Off',
],
'host value matches server name if compared case insensitive 1' => [
'httpHost' => 'secure.web.server',
'serverName' => 'secure.WEB.server',
'isAllowed' => true,
],
'host value matches server name if compared case insensitive 2' => [
'httpHost' => 'secure.WEB.server',
'serverName' => 'secure.web.server',
'isAllowed' => true,
],
'host value matches server name and server port is default https' => [
'httpHost' => 'secure.web.server',
'serverName' => 'secure.web.server',
'isAllowed' => true,
'serverPort' => '443',
'ssl' => 'On',
],
'host value matches server name and server port' => [
'httpHost' => 'secure.web.server:88',
'serverName' => 'secure.web.server',
'isAllowed' => true,
'serverPort' => '88',
],
'host value matches server name case insensitive 1 and server port' => [
'httpHost' => 'secure.WEB.server:88',
'serverName' => 'secure.web.server',
'isAllowed' => true,
'serverPort' => '88',
],
'host value matches server name case insensitive 2 and server port' => [
'httpHost' => 'secure.web.server:88',
'serverName' => 'secure.WEB.server',
'isAllowed' => true,
'serverPort' => '88',
],
'host value is ipv6 but matches server name and server port' => [
'httpHost' => '[::1]:81',
'serverName' => '[::1]',
'isAllowed' => true,
'serverPort' => '81',
],
'host value does not match server name' => [
'httpHost' => 'insecure.web.server',
'serverName' => 'secure.web.server',
'isAllowed' => false,
],
'host value does not match server port' => [
'httpHost' => 'secure.web.server:88',
'serverName' => 'secure.web.server',
'isAllowed' => false,
'serverPort' => '89',
],
'host value has default port that does not match server port' => [
'httpHost' => 'secure.web.server',
'serverName' => 'secure.web.server',
'isAllowed' => false,
'serverPort' => '81',
'ssl' => 'Off',
],
'host value has default port that does not match server ssl port' => [
'httpHost' => 'secure.web.server',
'serverName' => 'secure.web.server',
'isAllowed' => false,
'serverPort' => '444',
'ssl' => 'On',
],
];
}

/**
* @param string $httpHost
* @param string $serverName
* @param bool $isAllowed
* @param string $serverPort
* @param string $ssl
*
* @test
* @dataProvider serverNamePatternDataProvider
*/
public function isAllowedHostHeaderValueWorksCorrectlyWithWithServerNamePattern(
string $httpHost,
string $serverName,
bool $isAllowed,
string $serverPort = '80',
string $ssl = 'Off'
): void {
$serverParams = $_SERVER;
$serverParams['SERVER_NAME'] = $serverName;
$serverParams['SERVER_PORT'] = $serverPort;
$serverParams['HTTPS'] = $ssl;

$subject = new VerifyHostHeader(VerifyHostHeader::ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME);

self::assertSame($isAllowed, $subject->isAllowedHostHeaderValue($httpHost, $serverParams));
}

/**
* @param string $httpHost
* @param string $serverName
* @param bool $isAllowed
* @param string $serverPort
* @param string $ssl
*
* @test
* @dataProvider serverNamePatternDataProvider
*/
public function isAllowedHostHeaderValueWorksCorrectlyWithWithServerNamePatternAndSslProxy(
string $httpHost,
string $serverName,
bool $isAllowed,
string $serverPort = '80',
string $ssl = 'Off'
): void {
$serverParams = $_SERVER;
$serverParams['REMOTE_ADDR'] = '10.0.0.1';
$serverParams['SERVER_NAME'] = $serverName;
$serverParams['SERVER_PORT'] = $serverPort;
$serverParams['HTTPS'] = $ssl;

$subject = new VerifyHostHeader(VerifyHostHeader::ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME);

self::assertSame($isAllowed, $subject->isAllowedHostHeaderValue($httpHost, $serverParams));
}

/**
* @param string $httpHost HTTP_HOST string
* @param string $hostNamePattern trusted hosts pattern
* @test
* @dataProvider hostnamesNotMatchingTrustedHostsConfigurationDataProvider
*/
public function processThrowsExceptionForNotAllowedHostnameValues(string $httpHost, string $hostNamePattern): void
{
$this->expectException(\UnexpectedValueException::class);
$this->expectExceptionCode(1396795884);

$serverParams = $_SERVER;
$serverParams['HTTP_HOST'] = $httpHost;

$subject = new VerifyHostHeader($hostNamePattern);

$requestProphecy = $this->prophesize(ServerRequestInterface::class);
$requestProphecy->getServerParams()->willReturn($serverParams);

$requestHandlerProphecy = $this->prophesize(RequestHandlerInterface::class);

$subject->process($requestProphecy->reveal(), $requestHandlerProphecy->reveal());
}

/**
* @param string $httpHost HTTP_HOST string
* @param string $hostNamePattern trusted hosts pattern (not used in this test currently)
* @test
* @dataProvider hostnamesNotMatchingTrustedHostsConfigurationDataProvider
*/
public function processAllowsAllHostnameValuesIfHostPatternIsSetToAllowAll(string $httpHost, string $hostNamePattern): void
{
$serverParams = $_SERVER;
$serverParams['HTTP_HOST'] = $httpHost;

$subject = new VerifyHostHeader(VerifyHostHeader::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL);
$requestProphecy = $this->prophesize(ServerRequestInterface::class);
$requestProphecy->getServerParams()->willReturn($serverParams);

$responseProphecy = $this->prophesize(ResponseInterface::class);

$requestHandlerProphecy = $this->prophesize(RequestHandlerInterface::class);
$requestHandlerProphecy->handle($requestProphecy)->willReturn($responseProphecy->reveal())->shouldBeCalled();

$subject->process($requestProphecy->reveal(), $requestHandlerProphecy->reveal());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,38 +24,6 @@
*/
class GeneralUtilityFixture extends GeneralUtility
{
public static int $isAllowedHostHeaderValueCallCount = 0;

/**
* Tracks number of calls done to this method
*
* @param string $hostHeaderValue Host name without port
* @return bool
*/
public static function isAllowedHostHeaderValue($hostHeaderValue): bool
{
self::$isAllowedHostHeaderValueCallCount++;
return parent::isAllowedHostHeaderValue($hostHeaderValue);
}

/**
* @param bool $allowHostHeaderValue
*/
public static function setAllowHostHeaderValue(bool $allowHostHeaderValue): void
{
static::$allowHostHeaderValue = $allowHostHeaderValue;
}

/**
* For testing we must not generally allow HTTP Host headers
*
* @return bool
*/
protected static function isInternalRequestType(): bool
{
return false;
}

/**
* Resets the internal computed class name cache.
*/
Expand Down
247 changes: 0 additions & 247 deletions typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,6 @@ class GeneralUtilityTest extends UnitTestCase
protected function setUp(): void
{
parent::setUp();
GeneralUtilityFixture::$isAllowedHostHeaderValueCallCount = 0;
GeneralUtilityFixture::setAllowHostHeaderValue(false);
$GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL;
$this->backupPackageManager = ExtensionManagementUtilityAccessibleProxy::getPackageManager();
}

Expand Down Expand Up @@ -1294,250 +1291,6 @@ public function getIndpEnvTypo3HostOnlyParsesHostnamesAndIpAddresses($httpHost,
self::assertEquals($expectedIp, GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'));
}

/**
* @test
*/
public function isAllowedHostHeaderValueReturnsFalseIfTrustedHostsIsNotConfigured(): void
{
unset($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern']);
self::assertFalse(GeneralUtilityFixture::isAllowedHostHeaderValue('evil.foo.bar'));
}

/**
* @return array
*/
public static function hostnamesMatchingTrustedHostsConfigurationDataProvider(): array
{
return [
'hostname without port matching' => ['lolli.did.this', '.*\.did\.this'],
'other hostname without port matching' => ['helmut.did.this', '.*\.did\.this'],
'two different hostnames without port matching 1st host' => ['helmut.is.secure', '(helmut\.is\.secure|lolli\.is\.secure)'],
'two different hostnames without port matching 2nd host' => ['lolli.is.secure', '(helmut\.is\.secure|lolli\.is\.secure)'],
'hostname with port matching' => ['lolli.did.this:42', '.*\.did\.this:42'],
'hostnames are case insensitive 1' => ['lolli.DID.this:42', '.*\.did.this:42'],
'hostnames are case insensitive 2' => ['lolli.did.this:42', '.*\.DID.this:42'],
];
}

/**
* @return array
*/
public static function hostnamesNotMatchingTrustedHostsConfigurationDataProvider(): array
{
return [
'hostname without port' => ['lolli.did.this', 'helmut\.did\.this'],
'hostname with port, but port not allowed' => ['lolli.did.this:42', 'helmut\.did\.this'],
'two different hostnames in pattern but host header starts with different value #1' => ['sub.helmut.is.secure', '(helmut\.is\.secure|lolli\.is\.secure)'],
'two different hostnames in pattern but host header starts with different value #2' => ['sub.lolli.is.secure', '(helmut\.is\.secure|lolli\.is\.secure)'],
'two different hostnames in pattern but host header ends with different value #1' => ['helmut.is.secure.tld', '(helmut\.is\.secure|lolli\.is\.secure)'],
'two different hostnames in pattern but host header ends with different value #2' => ['lolli.is.secure.tld', '(helmut\.is\.secure|lolli\.is\.secure)'],
];
}

/**
* @param string $httpHost HTTP_HOST string
* @param string $hostNamePattern trusted hosts pattern
* @test
* @dataProvider hostnamesMatchingTrustedHostsConfigurationDataProvider
*/
public function isAllowedHostHeaderValueReturnsTrueIfHostValueMatches(string $httpHost, string $hostNamePattern): void
{
$GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = $hostNamePattern;
self::assertTrue(GeneralUtilityFixture::isAllowedHostHeaderValue($httpHost));
}

/**
* @param string $httpHost HTTP_HOST string
* @param string $hostNamePattern trusted hosts pattern
* @test
* @dataProvider hostnamesNotMatchingTrustedHostsConfigurationDataProvider
*/
public function isAllowedHostHeaderValueReturnsFalseIfHostValueMatches(string $httpHost, string $hostNamePattern): void
{
$GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = $hostNamePattern;
self::assertFalse(GeneralUtilityFixture::isAllowedHostHeaderValue($httpHost));
}

public function serverNamePatternDataProvider(): array
{
return [
'host value matches server name and server port is default http' => [
'httpHost' => 'secure.web.server',
'serverName' => 'secure.web.server',
'isAllowed' => true,
'serverPort' => '80',
'ssl' => 'Off',
],
'host value matches server name if compared case insensitive 1' => [
'httpHost' => 'secure.web.server',
'serverName' => 'secure.WEB.server',
'isAllowed' => true,
],
'host value matches server name if compared case insensitive 2' => [
'httpHost' => 'secure.WEB.server',
'serverName' => 'secure.web.server',
'isAllowed' => true,
],
'host value matches server name and server port is default https' => [
'httpHost' => 'secure.web.server',
'serverName' => 'secure.web.server',
'isAllowed' => true,
'serverPort' => '443',
'ssl' => 'On',
],
'host value matches server name and server port' => [
'httpHost' => 'secure.web.server:88',
'serverName' => 'secure.web.server',
'isAllowed' => true,
'serverPort' => '88',
],
'host value matches server name case insensitive 1 and server port' => [
'httpHost' => 'secure.WEB.server:88',
'serverName' => 'secure.web.server',
'isAllowed' => true,
'serverPort' => '88',
],
'host value matches server name case insensitive 2 and server port' => [
'httpHost' => 'secure.web.server:88',
'serverName' => 'secure.WEB.server',
'isAllowed' => true,
'serverPort' => '88',
],
'host value is ipv6 but matches server name and server port' => [
'httpHost' => '[::1]:81',
'serverName' => '[::1]',
'isAllowed' => true,
'serverPort' => '81',
],
'host value does not match server name' => [
'httpHost' => 'insecure.web.server',
'serverName' => 'secure.web.server',
'isAllowed' => false,
],
'host value does not match server port' => [
'httpHost' => 'secure.web.server:88',
'serverName' => 'secure.web.server',
'isAllowed' => false,
'serverPort' => '89',
],
'host value has default port that does not match server port' => [
'httpHost' => 'secure.web.server',
'serverName' => 'secure.web.server',
'isAllowed' => false,
'serverPort' => '81',
'ssl' => 'Off',
],
'host value has default port that does not match server ssl port' => [
'httpHost' => 'secure.web.server',
'serverName' => 'secure.web.server',
'isAllowed' => false,
'serverPort' => '444',
'ssl' => 'On',
],
];
}

/**
* @param string $httpHost
* @param string $serverName
* @param bool $isAllowed
* @param string $serverPort
* @param string $ssl
*
* @test
* @dataProvider serverNamePatternDataProvider
*/
public function isAllowedHostHeaderValueWorksCorrectlyWithWithServerNamePattern(
string $httpHost,
string $serverName,
bool $isAllowed,
string $serverPort = '80',
string $ssl = 'Off'
): void {
$GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME;
$_SERVER['SERVER_NAME'] = $serverName;
$_SERVER['SERVER_PORT'] = $serverPort;
$_SERVER['HTTPS'] = $ssl;
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(
string $httpHost,
string $serverName,
bool $isAllowed,
string $serverPort = '80',
string $ssl = 'Off'
): void {
$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
*/
public function allGetIndpEnvCallsRelatedToHostNamesCallIsAllowedHostHeaderValue(): void
{
GeneralUtilityFixture::getIndpEnv('HTTP_HOST');
GeneralUtility::flushInternalRuntimeCaches();
GeneralUtilityFixture::getIndpEnv('TYPO3_HOST_ONLY');
GeneralUtility::flushInternalRuntimeCaches();
GeneralUtilityFixture::getIndpEnv('TYPO3_REQUEST_HOST');
GeneralUtility::flushInternalRuntimeCaches();
GeneralUtilityFixture::getIndpEnv('TYPO3_REQUEST_URL');
self::assertSame(4, GeneralUtilityFixture::$isAllowedHostHeaderValueCallCount);
}

/**
* @param string $httpHost HTTP_HOST string
* @param string $hostNamePattern trusted hosts pattern
* @test
* @dataProvider hostnamesNotMatchingTrustedHostsConfigurationDataProvider
*/
public function getIndpEnvForHostThrowsExceptionForNotAllowedHostnameValues(string $httpHost, string $hostNamePattern): void
{
$this->expectException(\UnexpectedValueException::class);
$this->expectExceptionCode(1396795884);
$_SERVER['HTTP_HOST'] = $httpHost;
$GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = $hostNamePattern;
GeneralUtilityFixture::getIndpEnv('HTTP_HOST');
}

/**
* @param string $httpHost HTTP_HOST string
* @param string $hostNamePattern trusted hosts pattern (not used in this test currently)
* @test
* @dataProvider hostnamesNotMatchingTrustedHostsConfigurationDataProvider
*/
public function getIndpEnvForHostAllowsAllHostnameValuesIfHostPatternIsSetToAllowAll(string $httpHost, string $hostNamePattern): void
{
$_SERVER['HTTP_HOST'] = $httpHost;
$GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL;
self::assertSame($httpHost, GeneralUtility::getIndpEnv('HTTP_HOST'));
}

/**
* @test
* @dataProvider hostnameAndPortDataProvider
Expand Down
9 changes: 8 additions & 1 deletion typo3/sysext/frontend/Configuration/RequestMiddlewares.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,17 @@
'target' => \TYPO3\CMS\Frontend\Middleware\TimeTrackerInitialization::class,
],
/** internal: do not use or reference this middleware in your own code */
'typo3/cms-core/verify-host-header' => [
'target' => \TYPO3\CMS\Core\Middleware\VerifyHostHeader::class,
'after' => [
'typo3/cms-frontend/timetracker',
],
],
/** internal: do not use or reference this middleware in your own code */
'typo3/cms-core/normalized-params-attribute' => [
'target' => \TYPO3\CMS\Core\Middleware\NormalizedParamsAttribute::class,
'after' => [
'typo3/cms-frontend/timetracker',
'typo3/cms-core/verify-host-header',
],
],
/** internal: do not use or reference this middleware in your own code, as this will be possibly be removed */
Expand Down
24 changes: 20 additions & 4 deletions typo3/sysext/install/Classes/Controller/InstallerController.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
use TYPO3\CMS\Core\Information\Typo3Version;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
use TYPO3\CMS\Core\Middleware\VerifyHostHeader;
use TYPO3\CMS\Core\Package\FailsafePackageManager;
use TYPO3\CMS\Core\Registry;
use TYPO3\CMS\Core\Utility\GeneralUtility;
Expand Down Expand Up @@ -111,6 +112,11 @@ class InstallerController
*/
private $packageManager;

/**
* @var VerifyHostHeader
*/
private $verifyHostHeader;

/**
* @var PermissionsCheck
*/
Expand All @@ -124,6 +130,7 @@ public function __construct(
SiteConfiguration $siteConfiguration,
Registry $registry,
FailsafePackageManager $packageManager,
VerifyHostHeader $verifyHostHeader,
PermissionsCheck $databasePermissionsCheck
) {
$this->lateBootService = $lateBootService;
Expand All @@ -133,6 +140,7 @@ public function __construct(
$this->siteConfiguration = $siteConfiguration;
$this->registry = $registry;
$this->packageManager = $packageManager;
$this->verifyHostHeader = $verifyHostHeader;
$this->databasePermissionsCheck = $databasePermissionsCheck;
}

Expand Down Expand Up @@ -265,23 +273,31 @@ public function executeEnvironmentAndFoldersAction(): ResponseInterface
/**
* Check if trusted hosts pattern needs to be adjusted
*
* @param ServerRequestInterface $request
* @return ResponseInterface
*/
public function checkTrustedHostsPatternAction(): ResponseInterface
public function checkTrustedHostsPatternAction(ServerRequestInterface $request): ResponseInterface
{
$serverParams = $request->getServerParams();
$host = $serverParams['HTTP_HOST'] ?? '';

return new JsonResponse([
'success' => GeneralUtility::hostHeaderValueMatchesTrustedHostsPattern($_SERVER['HTTP_HOST']),
'success' => $this->verifyHostHeader->isAllowedHostHeaderValue($host, $serverParams),
]);
}

/**
* Adjust trusted hosts pattern to '.*' if it does not match yet
*
* @param ServerRequestInterface $request
* @return ResponseInterface
*/
public function executeAdjustTrustedHostsPatternAction(): ResponseInterface
public function executeAdjustTrustedHostsPatternAction(ServerRequestInterface $request): ResponseInterface
{
if (!GeneralUtility::hostHeaderValueMatchesTrustedHostsPattern($_SERVER['HTTP_HOST'])) {
$serverParams = $request->getServerParams();
$host = $serverParams['HTTP_HOST'] ?? '';

if (!$this->verifyHostHeader->isAllowedHostHeaderValue($host, $serverParams)) {
$this->configurationManager->setLocalConfigurationValueByPath('SYS/trustedHostsPattern', '.*');
}
return new JsonResponse([
Expand Down
2 changes: 2 additions & 0 deletions typo3/sysext/install/Classes/ServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Middleware\NormalizedParamsAttribute as NormalizedParamsMiddleware;
use TYPO3\CMS\Core\Middleware\ResponsePropagation as ResponsePropagationMiddleware;
use TYPO3\CMS\Core\Middleware\VerifyHostHeader;
use TYPO3\CMS\Core\Package\AbstractServiceProvider;
use TYPO3\CMS\Core\Package\FailsafePackageManager;
use TYPO3\CMS\Core\Package\PackageManager;
Expand Down Expand Up @@ -240,6 +241,7 @@ public static function getInstallerController(ContainerInterface $container): Co
$container->get(SiteConfiguration::class),
$container->get(Registry::class),
$container->get(FailsafePackageManager::class),
$container->get(VerifyHostHeader::class),
$container->get(PermissionsCheck::class)
);
}
Expand Down
6 changes: 4 additions & 2 deletions typo3/sysext/install/Classes/SystemEnvironment/SetupCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
use TYPO3\CMS\Core\Middleware\VerifyHostHeader;
use TYPO3\CMS\Core\Service\OpcodeCacheService;
use TYPO3\CMS\Core\Utility\GeneralUtility;

Expand Down Expand Up @@ -64,7 +65,7 @@ public function getStatus(): FlashMessageQueue
*/
protected function checkTrustedHostPattern()
{
if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {
if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === VerifyHostHeader::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {
$this->messageQueue->enqueue(new FlashMessage(
'Trusted hosts pattern is configured to allow all header values. Check the pattern defined in Admin'
. ' Tools -> Settings -> Configure Installation-Wide Options -> System -> trustedHostsPattern'
Expand All @@ -73,7 +74,8 @@ protected function checkTrustedHostPattern()
FlashMessage::WARNING
));
} else {
if (GeneralUtility::hostHeaderValueMatchesTrustedHostsPattern($_SERVER['HTTP_HOST'])) {
$verifyHostHeader = new VerifyHostHeader($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] ?? '');
if ($verifyHostHeader->isAllowedHostHeaderValue($_SERVER['HTTP_HOST'], $_SERVER)) {
$this->messageQueue->enqueue(new FlashMessage(
'',
'Trusted hosts pattern is configured to allow current host value.'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,4 +442,14 @@
'Breaking-93003-LimitationOfPageRendererToOnlyRenderFullPage.rst',
],
],
'TYPO3\CMS\Core\Utility\GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL' => [
'restFiles' => [
'Deprecation-95395-GeneralUtilityIsAllowedHostHeaderValueAndTrustedHostsPatternConstants.rst',
],
],
'TYPO3\CMS\Core\Utility\GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME' => [
'restFiles' => [
'Deprecation-95395-GeneralUtilityIsAllowedHostHeaderValueAndTrustedHostsPatternConstants.rst',
],
],
];
Original file line number Diff line number Diff line change
Expand Up @@ -1261,4 +1261,11 @@
'Deprecation-95367-GeneralUtilityisAbsPath.rst',
],
],
'TYPO3\CMS\Core\Utility\GeneralUtility::isAllowedHostHeaderValue' => [
'numberOfMandatoryArguments' => 1,
'maximumNumberOfArguments' => 1,
'restFiles' => [
'Deprecation-95395-GeneralUtilityIsAllowedHostHeaderValueAndTrustedHostsPatternConstants.rst',
],
],
];
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Middleware\VerifyHostHeader;
use TYPO3\CMS\Core\Resource\Security\FileNameValidator;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Reports\RequestAwareStatusProviderInterface;
Expand Down Expand Up @@ -128,7 +129,7 @@ protected function getTrustedHostsPatternStatus(): ReportStatus
$message = '';
$severity = ReportStatus::OK;

if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {
if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === VerifyHostHeader::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {
$value = $this->getLanguageService()->getLL('status_insecure');
$severity = ReportStatus::ERROR;
$message = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:warning.install_trustedhosts');
Expand Down