CVE-2021-41114 (published 2021-10-05T18:15Z)

TYPO3 is an open source PHP based web content management system released under the GNU GPL. It has been discovered that TYPO3 CMS is susceptible to host spoofing due to improper validation of the HTTP Host header. TYPO3 uses the HTTP Host header, for example, to generate absolute URLs during the frontend rendering process. Since the host header itself is provided by the client, it can be forged to any value, even in a name-based virtual hosts environment. This vulnerability is the same as described in TYPO3-CORE-SA-2014-001 (CVE-2014-3941). A regression, introduced during TYPO3 v11 development, led to this situation. The already existing setting $GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] (used as an effective mitigation strategy in previous TYPO3 versions) was not evaluated anymore, and reintroduced the vulnerability.

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:N/I:P/A:N)
CVSS v3 base score: 5.3, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N)

Weakness information:
cwe_id: CWE-20
cwe_name: Improper Input Validation
description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
extended_description: 
	   Input validation is a frequently-used technique
	   for checking potentially dangerous inputs in order to
	   ensure that the inputs are safe for processing within the
	   code, or when communicating with other components.  When
	   software does not validate input properly, an attacker is
	   able to craft the input in a form that is not expected by
	   the rest of the application. This will lead to parts of the
	   system receiving unintended input, which may result in
	   altered control flow, arbitrary control of a resource, or
	   arbitrary code execution.
	   Input validation is not the only technique for
	   processing input, however.  Other techniques attempt to
	   transform potentially-dangerous input into something safe, such
	   as filtering (CWE-790) - which attempts to remove dangerous
	   inputs - or encoding/escaping (CWE-116), which attempts to
	   ensure that the input is not misinterpreted when it is included
	   in output to another component. Other techniques exist as well
	   (see CWE-138 for more examples.)
	   Input validation can be applied to:
	   
	     raw data - strings, numbers, parameters, file contents, etc.
	     metadata - information about the raw data, such as headers or size
	   
	   Data can be simple or structured.  Structured data
	   can be composed of many nested layers, composed of
	   combinations of metadata and raw data, with other simple or
	   structured data.
	   Many properties of raw data or metadata may need
	   to be validated upon entry into the code, such
	   as:
	   
	     specified quantities such as size, length, frequency, price, rate, number of operations, time, etc.
	     implied or derived quantities, such as the actual size of a file instead of a specified size
	     indexes, offsets, or positions into more complex data structures
	     symbolic keys or other elements into hash tables, associative arrays, etc.
	     well-formedness, i.e. syntactic correctness - compliance with expected syntax 
	     lexical token correctness - compliance with rules for what is treated as a token
	     specified or derived type - the actual type of the input (or what the input appears to be)
	     consistency - between individual data elements, between raw data and metadata, between references, etc.
	     conformance to domain-specific rules, e.g. business logic 
	     equivalence - ensuring that equivalent inputs are treated the same
	     authenticity, ownership, or other attestations about the input, e.g. a cryptographic signature to prove the source of the data
	   
	   Implied or derived properties of data must often
	   be calculated or inferred by the code itself.  Errors in
	   deriving properties may be considered a contributing factor
	   to improper input validation.
	   
	   Note that "input validation" has very different
	   meanings to different people, or within different
	   classification schemes.  Caution must be used when
	   referencing this CWE entry or mapping to it.  For example,
	   some weaknesses might involve inadvertently giving control
	   to an attacker over an input when they should not be able
	   to provide an input at all, but sometimes this is referred
	   to as input validation.
	   Finally, it is important to emphasize that the
	   distinctions between input validation and output escaping
	   are often blurred, and developers must be careful to
	   understand the difference, including how input validation
	   is not always sufficient to prevent vulnerabilities,
	   especially when less stringent data types must be
	   supported, such as free-form text. Consider a SQL injection
	   scenario in which a person's last name is inserted into a
	   query. The name "O'Reilly" would likely pass the validation
	   step since it is a common last name in the English
	   language. However, this valid name cannot be directly
	   inserted into the database because it contains the "'"
	   apostrophe character, which would need to be escaped or
	   otherwise transformed. In this case, removing the
	   apostrophe might reduce the risk of SQL injection, but it
	   would produce incorrect behavior because the wrong name
	   would be recorded.
       
         
cwe_id: CWE-644
cwe_name: Improper Neutralization of HTTP Headers for Scripting Syntax
description: The product does not neutralize or incorrectly neutralizes web scripting syntax in HTTP headers that can be used by web browser components that can process raw headers, such as Flash.
extended_description: 
            An attacker may be able to conduct cross-site scripting and other attacks against users who have these components enabled.
            If a product does not neutralize user controlled data being placed in the header of an HTTP response coming from the server, the header may contain a script that will get executed in the client's browser context, potentially resulting in a cross site scripting vulnerability or possibly an HTTP response splitting attack. It is important to carefully control data that is being placed both in HTTP response header and in the HTTP response body to ensure that no scripting syntax is present, taking various encodings into account.
         
         

Fix information:
repo_url: https://github.com/TYPO3/typo3
commit_details: repo_url: https://github.com/TYPO3/typo3
author: Benjamin Franzke
author_date: 2021-10-05 13:02:21+02:00
msg: [SECURITY] Verify HTTP_HOST via FE/BE middleware

Avoid a dependency cycle between HTTP_HOST generation
and verification.
As $GLOBALS['TYPO3_REQUEST'] is not available
during ServerRequestFactory::fromGlobals(), HTTP_HOST
verification can not be performed at that point.
It is therefore delayed into a context aware middleware
instead of being skipped because of missing $GLOBALS.

Positive advantage of moving the verification into
frontend and backend middlewares, is that context
checks to exclude CLI/installtool can be dropped.

As a side effect this also fixes the frontend to installtool
redirect if TYPO3 is not yet configured and running with
an invalid SERVER_NAME, as ServerRequestFactory::fromGlobals()
doesn't fail.

Releases: master
Resolves: #95395
Change-Id: Idd3a3449a878cd625dad0d04892d9f0e710ca1a9
Security-Bulletin: TYPO3-CORE-SA-2021-015
Security-References: CVE-2021-41114
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/71438
Tested-by: Oliver Hader <oliver.hader@typo3.org>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
repository: repo_url: https://github.com/TYPO3/typo3
repo_name: TYPO3/typo3
description: The TYPO3 Core - Enterprise Content Management System. Synchronized mirror of https://review.typo3.org/q/project:Packages/TYPO3.CMS
repo_language: PHP
owner: TYPO3
file_changes: filename: RequestMiddlewares.php
change_type: ModificationType.MODIFY
diff: @@ -11,9 +11,16 @@
  */
 return [
     'backend' => [
+        /** 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,
+        ],
         /** 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-core/verify-host-header',
+            ],
         ],
         'typo3/cms-backend/locked-backend' => [
             'target' => \TYPO3\CMS\Backend\Middleware\LockedBackendGuard::class,

programming_language: PHP
filename: NormalizedParams.php
change_type: ModificationType.MODIFY
diff: @@ -548,13 +548,12 @@ class NormalizedParams
     }
 
     /**
-     * Sanitize HTTP_HOST, take proxy configuration into account and
-     * verify allowed hosts with configured trusted hosts pattern.
+     * Normalize HTTP_HOST by taking proxy configuration into account.
      *
      * @param array $serverParams Basically the $_SERVER, but from $request object
      * @param array $configuration $TYPO3_CONF_VARS['SYS'] array
      * @param bool $isBehindReverseProxy True if reverse proxy setup is detected
-     * @return string Sanitized HTTP_HOST
+     * @return string Normalized HTTP_HOST
      */
     protected static function determineHttpHost(
         array $serverParams,
@@ -585,14 +584,6 @@ class NormalizedParams
                 $httpHost = $xForwardedHost;
             }
         }
-        if (!GeneralUtility::isAllowedHostHeaderValue($httpHost)) {
-            throw new \UnexpectedValueException(
-                'The current host header value does not match the configured trusted hosts pattern!'
-                . ' Check the pattern defined in $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'trustedHostsPattern\']'
-                . ' and adapt it, if you want to allow the current host header \'' . $httpHost . '\' for your installation.',
-                1396795886
-            );
-        }
         return $httpHost;
     }
 

programming_language: PHP
filename: VerifyHostHeader.php
change_type: ModificationType.ADD
diff: @@ -0,0 +1,126 @@
+<?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\Middleware;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+
+/**
+ * Checks if the provided host header value matches the trusted hosts pattern.
+ *
+ * @internal
+ */
+class VerifyHostHeader implements MiddlewareInterface
+{
+    public const ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL = '.*';
+    public const ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME = 'SERVER_NAME';
+
+    protected string $trustedHostsPattern;
+
+    public function __construct(string $trustedHostsPattern)
+    {
+        $this->trustedHostsPattern = $trustedHostsPattern;
+    }
+
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        $serverParams = $request->getServerParams();
+        $httpHost = $serverParams['HTTP_HOST'] ?? '';
+        if (!$this->isAllowedHostHeaderValue($httpHost, $serverParams)) {
+            throw new \UnexpectedValueException(
+                'The current host header value does not match the configured trusted hosts pattern!'
+                . ' Check the pattern defined in $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'trustedHostsPattern\']'
+                . ' and adapt it, if you want to allow the current host header \'' . $httpHost . '\' for your installation.',
+                1396795884
+            );
+        }
+
+        return $handler->handle($request);
+    }
+
+    /**
+     * Checks if the provided host header value matches the trusted hosts pattern.
+     *
+     * @param string $hostHeaderValue HTTP_HOST header value as sent during the request (may include port)
+     * @return bool
+     */
+    public function isAllowedHostHeaderValue(string $hostHeaderValue, array $serverParams): bool
+    {
+        // Deny the value if trusted host patterns is empty, which means configuration is invalid.
+        if ($this->trustedHostsPattern === '') {
+            return false;
+        }
+
+        if ($this->trustedHostsPattern === self::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {
+            return true;
+        }
+
+        return $this->hostHeaderValueMatchesTrustedHostsPattern($hostHeaderValue, $serverParams);
+    }
+
+    /**
+     * Checks if the provided host header value matches the trusted hosts pattern without any preprocessing.
+     */
+    protected function hostHeaderValueMatchesTrustedHostsPattern(string $hostHeaderValue, array $serverParams): bool
+    {
+        if ($this->trustedHostsPattern === self::ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME) {
+            $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($serverParams) ? '443' : '80';
+
+            $parsedHostValue = parse_url('http://' . $host);
+            if (isset($parsedHostValue['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($serverParams['SERVER_NAME']) && $port === $serverParams['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
+            $hostMatch = (bool)preg_match('/^' . $this->trustedHostsPattern . '$/i', $hostHeaderValue);
+        }
+
+        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.
+     */
+    protected function webserverUsesHttps(array $serverParams): bool
+    {
+        if (!empty($serverParams['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($serverParams['HTTPS']) && strtolower($serverParams['HTTPS']) !== 'off';
+    }
+}

programming_language: PHP
filename: ServiceProvider.php
change_type: ModificationType.MODIFY
diff: @@ -70,6 +70,7 @@ class ServiceProvider extends AbstractServiceProvider
             Mail\TransportFactory::class => [ static::class, 'getMailTransportFactory' ],
             Messaging\FlashMessageService::class => [ static::class, 'getFlashMessageService' ],
             Middleware\ResponsePropagation::class => [ static::class, 'getResponsePropagationMiddleware' ],
+            Middleware\VerifyHostHeader::class => [ static::class, 'getVerifyHostHeaderMiddleware' ],
             Package\FailsafePackageManager::class => [ static::class, 'getFailsafePackageManager' ],
             Package\Cache\PackageDependentCacheIdentifier::class => [ static::class, 'getPackageDependentCacheIdentifier' ],
             Registry::class => [ static::class, 'getRegistry' ],
@@ -369,6 +370,13 @@ class ServiceProvider extends AbstractServiceProvider
         return self::new($container, Middleware\ResponsePropagation::class);
     }
 
+    public static function getVerifyHostHeaderMiddleware(ContainerInterface $container): Middleware\VerifyHostHeader
+    {
+        return self::new($container, Middleware\VerifyHostHeader::class, [
+            $GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] ?? '',
+        ]);
+    }
+
     public static function getFailsafePackageManager(ContainerInterface $container): Package\FailsafePackageManager
     {
         $packageManager = $container->get(Package\PackageManager::class);

programming_language: PHP
filename: GeneralUtility.php
change_type: ModificationType.MODIFY
diff: @@ -30,6 +30,7 @@ use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Http\ApplicationType;
 use TYPO3\CMS\Core\Http\RequestFactory;
 use TYPO3\CMS\Core\Log\LogManager;
+use TYPO3\CMS\Core\Middleware\VerifyHostHeader;
 use TYPO3\CMS\Core\Package\Exception as PackageException;
 use TYPO3\CMS\Core\SingletonInterface;
 
@@ -46,17 +47,11 @@ use TYPO3\CMS\Core\SingletonInterface;
  */
 class GeneralUtility
 {
+    /* @deprecated since v11, will be removed in v12. */
     const ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL = '.*';
+    /* @deprecated since v11, will be removed in v12. */
     const ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME = 'SERVER_NAME';
 
-    /**
-     * State of host header value security check
-     * in order to avoid unnecessary multiple checks during one request
-     *
-     * @var bool
-     */
-    protected static $allowHostHeaderValue = false;
-
     /**
      * @var ContainerInterface|null
      */
@@ -2563,12 +2558,6 @@ class GeneralUtility
                         $retVal = $host;
                     }
                 }
-                if (!static::isAllowedHostHeaderValue($retVal)) {
-                    throw new \UnexpectedValueException(
-                        'The current host header value does not match the configured trusted hosts pattern! Check the pattern defined in $GLOBALS[\'TYPO3_CONF_VARS\'][\'SYS\'][\'trustedHostsPattern\'] and adapt it, if you want to allow the current host header \'' . $retVal . '\' for your installation.',
-                        1396795884
-                    );
-                }
                 break;
             case 'HTTP_REFERER':
 
@@ -2695,68 +2684,17 @@ class GeneralUtility
 
     /**
      * Checks if the provided host header value matches the trusted hosts pattern.
-     * If the pattern is not defined (which only can happen early in the bootstrap), deny any value.
-     * The result is saved, so the check needs to be executed only once.
      *
      * @param string $hostHeaderValue HTTP_HOST header value as sent during the request (may include port)
      * @return bool
+     * @deprecated will be removed in TYPO3 v12.0.
      */
     public static function isAllowedHostHeaderValue($hostHeaderValue)
     {
-        if (static::$allowHostHeaderValue === true) {
-            return true;
-        }
-
-        if (static::isInternalRequestType()) {
-            return static::$allowHostHeaderValue = true;
-        }
-
-        // Deny the value if trusted host patterns is empty, which means we are early in the bootstrap
-        if (empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'])) {
-            return false;
-        }
-
-        if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === self::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {
-            static::$allowHostHeaderValue = true;
-        } else {
-            static::$allowHostHeaderValue = static::hostHeaderValueMatchesTrustedHostsPattern($hostHeaderValue);
-        }
-
-        return static::$allowHostHeaderValue;
-    }
-
-    /**
-     * Checks if the provided host header value matches the trusted hosts pattern without any preprocessing.
-     *
-     * @param string $hostHeaderValue
-     * @return bool
-     * @internal
-     */
-    public static function hostHeaderValueMatchesTrustedHostsPattern($hostHeaderValue)
-    {
-        if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === self::ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME) {
-            $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'])) {
-                $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
-            $hostMatch = (bool)preg_match('/^' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] . '$/i', $hostHeaderValue);
-        }
+        trigger_error('GeneralUtility::isAllowedHostHeaderValue() will be removed in TYPO3 v12.0. Host header is verified by frontend and backend middlewares.', E_USER_DEPRECATED);
 
-        return $hostMatch;
+        $verifyHostHeader = new VerifyHostHeader($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] ?? '');
+        return $verifyHostHeader->isAllowedHostHeaderValue($hostHeaderValue, $_SERVER);
     }
 
     /**
@@ -2780,24 +2718,6 @@ class GeneralUtility
         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.
-     * Also CLI needs to be allowed as unfortunately AbstractUserAuthentication::getAuthInfoArray()
-     * accesses HTTP_HOST without reason on CLI
-     * Additionally, allows requests when no REQUESTTYPE is set, which can happen quite early in the
-     * Bootstrap. See Application.php in EXT:backend/Classes/Http/.
-     *
-     * @return bool
-     */
-    protected static function isInternalRequestType()
-    {
-        return Environment::isCli()
-            || !isset($GLOBALS['TYPO3_REQUEST'])
-            || !($GLOBALS['TYPO3_REQUEST'] instanceof ServerRequestInterface)
-            || (bool)((int)($GLOBALS['TYPO3_REQUEST'])->getAttribute('applicationType') & TYPO3_REQUESTTYPE_INSTALL);
-    }
-
     /*************************
      *
      * TYPO3 SPECIFIC FUNCTIONS

programming_language: PHP
filename: Deprecation-95395-GeneralUtilityIsAllowedHostHeaderValueAndTrustedHostsPatternConstants.rst
change_type: ModificationType.ADD
diff: @@ -0,0 +1,53 @@
+.. include:: ../../Includes.txt
+
+====================================================================================================
+Deprecation: #95395 - GeneralUtility::isAllowedHostHeaderValue() and TRUSTED_HOSTS_PATTERN constants
+====================================================================================================
+
+See :issue:`95395`
+
+Description
+===========
+
+The PHP method
+:php:`TYPO3\CMS\Core\Utility\GeneralUtility::isAllowedHostHeaderValue()`
+and the PHP constants
+:php:`TYPO3\CMS\Core\Utility\GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL`
+and
+:php:`TYPO3\CMS\Core\Utility\GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME`
+have been deprecated.
+
+
+Impact
+======
+
+A deprecation will be logged in TYPO3 v11 if
+:php:`TYPO3\CMS\Core\Utility\GeneralUtility::isAllowedHostHeaderValue()` is
+used. It is unlikely for extensions to have used this as the host header
+is checked for every frontend and backend request anyway.
+
+Usage of the constants will cause a PHP error "Undefined class constant" in
+TYPO3 v12, the method
+:php:`TYPO3\CMS\Core\Utility\GeneralUtility::isAllowedHostHeaderValue()` will be
+dropped without replacement.
+
+
+Affected Installations
+======================
+
+Installations using the constants instead of static strings or
+call the method explictily – which is unlikely.
+
+
+Migration
+=========
+
+Use :php:`'.*'` instead of
+:php:`TYPO3\CMS\Core\Utility\GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL`
+and :php:`'SERVER_NAME'` instead of
+:php:`TYPO3\CMS\Core\Utility\GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_SERVER_NAME`.
+
+Don't use  :php:`TYPO3\CMS\Core\Utility\GeneralUtility::isAllowedHostHeaderValue()`.
+
+
+.. index:: PHP-API, FullyScanned, ext:core

programming_language: Markdown
filename: UpgradeCest.php
change_type: ModificationType.MODIFY
diff: @@ -85,8 +85,7 @@ class UpgradeCest extends AbstractCest
         $I->click($versionPanel . ' a[data-bs-toggle="collapse"]');
         $I->click($versionPanel . ' .t3js-upgradeDocs-markRead');
 
-        $textNewFirstPanelHeading = $I->grabTextFrom($versionPanel . ' .panel-heading');
-        $I->assertNotEquals($textCurrentFirstPanelHeading, $textNewFirstPanelHeading);
+        $I->dontSee($textCurrentFirstPanelHeading, '#version-1');
 
         $I->amGoingTo('mark an item as unread');
         $I->click('#heading-read');

programming_language: PHP
filename: VerifyHostHeaderTest.php
change_type: ModificationType.ADD
diff: @@ -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());
+    }
+}

programming_language: PHP
filename: GeneralUtilityFixture.php
change_type: ModificationType.MODIFY
diff: @@ -24,38 +24,6 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  */
 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.
      */

programming_language: PHP
filename: GeneralUtilityTest.php
change_type: ModificationType.MODIFY
diff: @@ -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();
     }
 
@@ -1294,250 +1291,6 @@ class GeneralUtilityTest extends UnitTestCase
         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

programming_language: PHP
filename: RequestMiddlewares.php
change_type: ModificationType.MODIFY
diff: @@ -16,10 +16,17 @@ return [
             '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 */

programming_language: PHP
filename: InstallerController.php
change_type: ModificationType.MODIFY
diff: @@ -49,6 +49,7 @@ use TYPO3\CMS\Core\Http\NormalizedParams;
 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;
@@ -111,6 +112,11 @@ class InstallerController
      */
     private $packageManager;
 
+    /**
+     * @var VerifyHostHeader
+     */
+    private $verifyHostHeader;
+
     /**
      * @var PermissionsCheck
      */
@@ -124,6 +130,7 @@ class InstallerController
         SiteConfiguration $siteConfiguration,
         Registry $registry,
         FailsafePackageManager $packageManager,
+        VerifyHostHeader $verifyHostHeader,
         PermissionsCheck $databasePermissionsCheck
     ) {
         $this->lateBootService = $lateBootService;
@@ -133,6 +140,7 @@ class InstallerController
         $this->siteConfiguration = $siteConfiguration;
         $this->registry = $registry;
         $this->packageManager = $packageManager;
+        $this->verifyHostHeader = $verifyHostHeader;
         $this->databasePermissionsCheck = $databasePermissionsCheck;
     }
 
@@ -265,23 +273,31 @@ class InstallerController
     /**
      * 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([

programming_language: PHP
filename: ServiceProvider.php
change_type: ModificationType.MODIFY
diff: @@ -34,6 +34,7 @@ use TYPO3\CMS\Core\Localization\Locales;
 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;
@@ -240,6 +241,7 @@ class ServiceProvider extends AbstractServiceProvider
             $container->get(SiteConfiguration::class),
             $container->get(Registry::class),
             $container->get(FailsafePackageManager::class),
+            $container->get(VerifyHostHeader::class),
             $container->get(PermissionsCheck::class)
         );
     }

programming_language: PHP
filename: SetupCheck.php
change_type: ModificationType.MODIFY
diff: @@ -18,6 +18,7 @@ namespace TYPO3\CMS\Install\SystemEnvironment;
 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;
 
@@ -64,7 +65,7 @@ class SetupCheck implements CheckInterface
      */
     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'
@@ -73,7 +74,8 @@ class SetupCheck implements CheckInterface
                 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.'

programming_language: PHP
filename: ClassConstantMatcher.php
change_type: ModificationType.MODIFY
diff: @@ -442,4 +442,14 @@ return [
             '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',
+        ],
+    ],
 ];

programming_language: PHP
filename: MethodCallStaticMatcher.php
change_type: ModificationType.MODIFY
diff: @@ -1261,4 +1261,11 @@ return [
             'Deprecation-95367-GeneralUtilityisAbsPath.rst',
         ],
     ],
+    'TYPO3\CMS\Core\Utility\GeneralUtility::isAllowedHostHeaderValue' => [
+        'numberOfMandatoryArguments' => 1,
+        'maximumNumberOfArguments' => 1,
+        'restFiles' => [
+            'Deprecation-95395-GeneralUtilityIsAllowedHostHeaderValueAndTrustedHostsPatternConstants.rst',
+        ],
+    ],
 ];

programming_language: PHP
filename: SecurityStatus.php
change_type: ModificationType.MODIFY
diff: @@ -25,6 +25,7 @@ use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
 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;
@@ -128,7 +129,7 @@ class SecurityStatus implements RequestAwareStatusProviderInterface
         $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');

programming_language: PHP