CVE-2021-41114: Improper Neutralization of HTTP Headers for Scripting Syntax (CWE-644)
value: 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.
CWE 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.
CVSS v3 base score: 5.3
Published: 2021-10-05T18:15Z
Repository: https://github.com/TYPO3/typo3
Language: PHP
Files changed: typo3/sysext/backend/Configuration/RequestMiddlewares.php, typo3/sysext/core/Classes/Http/NormalizedParams.php, typo3/sysext/core/Classes/Middleware/VerifyHostHeader.php, typo3/sysext/core/Classes/ServiceProvider.php, typo3/sysext/core/Classes/Utility/GeneralUtility.php, typo3/sysext/core/Documentation/Changelog/11.5/Deprecation-95395-GeneralUtilityIsAllowedHostHeaderValueAndTrustedHostsPatternConstants.rst, typo3/sysext/core/Tests/Acceptance/Application/InstallTool/UpgradeCest.php, typo3/sysext/core/Tests/Unit/Middleware/VerifyHostHeaderTest.php, typo3/sysext/core/Tests/Unit/Utility/Fixtures/GeneralUtilityFixture.php, typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php, typo3/sysext/frontend/Configuration/RequestMiddlewares.php, typo3/sysext/install/Classes/Controller/InstallerController.php, typo3/sysext/install/Classes/ServiceProvider.php, typo3/sysext/install/Classes/SystemEnvironment/SetupCheck.php, typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassConstantMatcher.php, typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php, typo3/sysext/reports/Classes/Report/Status/SecurityStatus.php
Commit date: 2021-10-05 13:02:47 +0200
Commit message: [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>

Diff: diff --git a/typo3/sysext/backend/Configuration/RequestMiddlewares.php b/typo3/sysext/backend/Configuration/RequestMiddlewares.php
index 00d02fc74af..cc5506d1c06 100644
--- a/typo3/sysext/backend/Configuration/RequestMiddlewares.php
+++ b/typo3/sysext/backend/Configuration/RequestMiddlewares.php
@@ -9,13 +9,20 @@
  *      ]
  *   ]
  */
 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,
             'after' => [
                 'typo3/cms-core/normalized-params-attribute',
diff --git a/typo3/sysext/core/Classes/Http/NormalizedParams.php b/typo3/sysext/core/Classes/Http/NormalizedParams.php
index fb236f20aea..938ccf7f40d 100644
--- a/typo3/sysext/core/Classes/Http/NormalizedParams.php
+++ b/typo3/sysext/core/Classes/Http/NormalizedParams.php
@@ -546,17 +546,16 @@ class NormalizedParams
     {
         return $this->queryString;
     }
 
     /**
-     * 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,
         array $configuration,
         bool $isBehindReverseProxy
@@ -583,18 +582,10 @@ class NormalizedParams
             }
             if ($xForwardedHost) {
                 $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;
     }
 
     /**
      * Determine if the client called via HTTPS. Takes proxy ssl terminator
diff --git a/typo3/sysext/core/Classes/Middleware/VerifyHostHeader.php b/typo3/sysext/core/Classes/Middleware/VerifyHostHeader.php
new file mode 100644
index 00000000000..b61b1ec81c1
--- /dev/null
+++ b/typo3/sysext/core/Classes/Middleware/VerifyHostHeader.php
@@ -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';
+    }
+}
diff --git a/typo3/sysext/core/Classes/ServiceProvider.php b/typo3/sysext/core/Classes/ServiceProvider.php
index ca605529bff..128a6921eca 100644
--- a/typo3/sysext/core/Classes/ServiceProvider.php
+++ b/typo3/sysext/core/Classes/ServiceProvider.php
@@ -68,10 +68,11 @@ class ServiceProvider extends AbstractServiceProvider
             Localization\Locales::class => [ static::class, 'getLocales' ],
             Localization\LocalizationFactory::class => [ static::class, 'getLocalizationFactory' ],
             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' ],
             Resource\Index\FileIndexRepository::class => [ static::class, 'getFileIndexRepository' ],
             Resource\Index\MetaDataRepository::class => [ static::class, 'getMetaDataRepository' ],
@@ -367,10 +368,17 @@ class ServiceProvider extends AbstractServiceProvider
     public static function getResponsePropagationMiddleware(ContainerInterface $container): Middleware\ResponsePropagation
     {
         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);
         if ($packageManager instanceof Package\FailsafePackageManager) {
             return $packageManager;
diff --git a/typo3/sysext/core/Classes/Utility/GeneralUtility.php b/typo3/sysext/core/Classes/Utility/GeneralUtility.php
index ffb7d11ec16..cf8663529a6 100644
--- a/typo3/sysext/core/Classes/Utility/GeneralUtility.php
+++ b/typo3/sysext/core/Classes/Utility/GeneralUtility.php
@@ -28,10 +28,11 @@ use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Core\ClassLoadingInformation;
 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;
 
 /**
  * The legendary "t3lib_div" class - Miscellaneous functions for general purpose.
@@ -44,21 +45,15 @@ use TYPO3\CMS\Core\SingletonInterface;
  * All methods in this class are meant to be called statically.
  * So use \TYPO3\CMS\Core\Utility\GeneralUtility::[method-name] to refer to the functions, eg. '\TYPO3\CMS\Core\Utility\GeneralUtility::milliseconds()'
  */
 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
      */
     protected static $container;
 
@@ -2561,16 +2556,10 @@ class GeneralUtility
                     }
                     if ($host) {
                         $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':
 
             case 'HTTP_USER_AGENT':
 
@@ -2693,72 +2682,21 @@ class GeneralUtility
         return $retVal;
     }
 
     /**
      * 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);
     }
 
     /**
      * Determine if the webserver uses HTTPS.
      *
@@ -2778,28 +2716,10 @@ class GeneralUtility
         // 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.
-     * 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
      *
      *************************/
diff --git a/typo3/sysext/core/Documentation/Changelog/11.5/Deprecation-95395-GeneralUtilityIsAllowedHostHeaderValueAndTrustedHostsPatternConstants.rst b/typo3/sysext/core/Documentation/Changelog/11.5/Deprecation-95395-GeneralUtilityIsAllowedHostHeaderValueAndTrustedHostsPatternConstants.rst
new file mode 100644
index 00000000000..0db8e35d70f
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/11.5/Deprecation-95395-GeneralUtilityIsAllowedHostHeaderValueAndTrustedHostsPatternConstants.rst
@@ -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
diff --git a/typo3/sysext/core/Tests/Acceptance/Application/InstallTool/UpgradeCest.php b/typo3/sysext/core/Tests/Acceptance/Application/InstallTool/UpgradeCest.php
index cb31a8d9b62..85f22888645 100644
--- a/typo3/sysext/core/Tests/Acceptance/Application/InstallTool/UpgradeCest.php
+++ b/typo3/sysext/core/Tests/Acceptance/Application/InstallTool/UpgradeCest.php
@@ -83,12 +83,11 @@ class UpgradeCest extends AbstractCest
         $textCurrentFirstPanelHeading = $I->grabTextFrom($versionPanel . ' .panel-heading');
 
         $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');
         $I->waitForElement('#collapseRead', 5, ModalDialog::$openedModalSelector);
         $I->executeJS('document.querySelector("#collapseRead").scrollIntoView();');
diff --git a/typo3/sysext/core/Tests/Unit/Middleware/VerifyHostHeaderTest.php b/typo3/sysext/core/Tests/Unit/Middleware/VerifyHostHeaderTest.php
new file mode 100644
index 00000000000..7bbadc4bdc7
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Middleware/VerifyHostHeaderTest.php
@@ -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());
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Utility/Fixtures/GeneralUtilityFixture.php b/typo3/sysext/core/Tests/Unit/Utility/Fixtures/GeneralUtilityFixture.php
index cb6c332a0e3..3b7c271b35c 100644
--- a/typo3/sysext/core/Tests/Unit/Utility/Fixtures/GeneralUtilityFixture.php
+++ b/typo3/sysext/core/Tests/Unit/Utility/Fixtures/GeneralUtilityFixture.php
@@ -22,42 +22,10 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 /**
  * Class GeneralUtilityFixture
  */
 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.
      */
     public static function resetFinalClassNameCache(): void
     {
diff --git a/typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php b/typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php
index 72cfdb07303..aa12dea4c72 100644
--- a/typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php
+++ b/typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php
@@ -69,13 +69,10 @@ class GeneralUtilityTest extends UnitTestCase
      * Set up
      */
     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();
     }
 
     /**
      * Tear down
@@ -1292,254 +1289,10 @@ class GeneralUtilityTest extends UnitTestCase
     {
         $_SERVER['HTTP_HOST'] = $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
      */
     public function getIndpEnvTypo3PortParsesHostnamesAndIpAddresses($httpHost, $dummy, $expectedPort): void
diff --git a/typo3/sysext/frontend/Configuration/RequestMiddlewares.php b/typo3/sysext/frontend/Configuration/RequestMiddlewares.php
index 7705828a5b0..a08fe5ed25f 100644
--- a/typo3/sysext/frontend/Configuration/RequestMiddlewares.php
+++ b/typo3/sysext/frontend/Configuration/RequestMiddlewares.php
@@ -14,14 +14,21 @@ return [
         /** internal: do not use or reference this middleware in your own code */
         'typo3/cms-frontend/timetracker' => [
             '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 */
         'typo3/cms-frontend/eid' => [
             'target' => \TYPO3\CMS\Frontend\Middleware\EidHandler::class,
diff --git a/typo3/sysext/install/Classes/Controller/InstallerController.php b/typo3/sysext/install/Classes/Controller/InstallerController.php
index 062af2a4dd6..5453cf98a58 100644
--- a/typo3/sysext/install/Classes/Controller/InstallerController.php
+++ b/typo3/sysext/install/Classes/Controller/InstallerController.php
@@ -47,10 +47,11 @@ use TYPO3\CMS\Core\Http\HtmlResponse;
 use TYPO3\CMS\Core\Http\JsonResponse;
 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;
 use TYPO3\CMS\Core\Utility\PathUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
@@ -109,10 +110,15 @@ class InstallerController
     /**
      * @var FailsafePackageManager
      */
     private $packageManager;
 
+    /**
+     * @var VerifyHostHeader
+     */
+    private $verifyHostHeader;
+
     /**
      * @var PermissionsCheck
      */
     private $databasePermissionsCheck;
 
@@ -122,19 +128,21 @@ class InstallerController
         SilentTemplateFileUpgradeService $silentTemplateFileUpgradeService,
         ConfigurationManager $configurationManager,
         SiteConfiguration $siteConfiguration,
         Registry $registry,
         FailsafePackageManager $packageManager,
+        VerifyHostHeader $verifyHostHeader,
         PermissionsCheck $databasePermissionsCheck
     ) {
         $this->lateBootService = $lateBootService;
         $this->silentConfigurationUpgradeService = $silentConfigurationUpgradeService;
         $this->silentTemplateFileUpgradeService = $silentTemplateFileUpgradeService;
         $this->configurationManager = $configurationManager;
         $this->siteConfiguration = $siteConfiguration;
         $this->registry = $registry;
         $this->packageManager = $packageManager;
+        $this->verifyHostHeader = $verifyHostHeader;
         $this->databasePermissionsCheck = $databasePermissionsCheck;
     }
 
     /**
      * Init action loads <head> with JS initiating further stuff
@@ -263,27 +271,35 @@ 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([
             'success' => true,
         ]);
diff --git a/typo3/sysext/install/Classes/ServiceProvider.php b/typo3/sysext/install/Classes/ServiceProvider.php
index 7cdd051762d..35a4df024f1 100644
--- a/typo3/sysext/install/Classes/ServiceProvider.php
+++ b/typo3/sysext/install/Classes/ServiceProvider.php
@@ -32,10 +32,11 @@ use TYPO3\CMS\Core\Imaging\IconRegistry;
 use TYPO3\CMS\Core\Localization\LanguageServiceFactory;
 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;
 use TYPO3\CMS\Core\Registry;
 use TYPO3\CMS\Core\Resource\ProcessedFileRepository;
@@ -238,10 +239,11 @@ class ServiceProvider extends AbstractServiceProvider
             $container->get(Service\SilentTemplateFileUpgradeService::class),
             $container->get(ConfigurationManager::class),
             $container->get(SiteConfiguration::class),
             $container->get(Registry::class),
             $container->get(FailsafePackageManager::class),
+            $container->get(VerifyHostHeader::class),
             $container->get(PermissionsCheck::class)
         );
     }
 
     public static function getLayoutController(ContainerInterface $container): Controller\LayoutController
diff --git a/typo3/sysext/install/Classes/SystemEnvironment/SetupCheck.php b/typo3/sysext/install/Classes/SystemEnvironment/SetupCheck.php
index e860b420a57..5096383ef83 100644
--- a/typo3/sysext/install/Classes/SystemEnvironment/SetupCheck.php
+++ b/typo3/sysext/install/Classes/SystemEnvironment/SetupCheck.php
@@ -16,10 +16,11 @@
 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;
 
 /**
  * Check TYPO3 setup status
@@ -62,20 +63,21 @@ class SetupCheck implements CheckInterface
     /**
      * Checks the status of the trusted hosts pattern check
      */
     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'
                     . ' and adapt it to expected host value(s).',
                 'Trusted hosts pattern is insecure',
                 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.'
                 ));
             } else {
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassConstantMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassConstantMatcher.php
index e542af02f7a..e90059fdd44 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassConstantMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ClassConstantMatcher.php
@@ -440,6 +440,16 @@ return [
     'TYPO3\CMS\Core\Page\PageRenderer::PART_FOOTER' => [
         'restFiles' => [
             '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',
+        ],
+    ],
 ];
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
index 5fe1a6b0ab3..4787b227609 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
@@ -1259,6 +1259,13 @@ return [
         'maximumNumberOfArguments' => 1,
         'restFiles' => [
             'Deprecation-95367-GeneralUtilityisAbsPath.rst',
         ],
     ],
+    'TYPO3\CMS\Core\Utility\GeneralUtility::isAllowedHostHeaderValue' => [
+        'numberOfMandatoryArguments' => 1,
+        'maximumNumberOfArguments' => 1,
+        'restFiles' => [
+            'Deprecation-95395-GeneralUtilityIsAllowedHostHeaderValueAndTrustedHostsPatternConstants.rst',
+        ],
+    ],
 ];
diff --git a/typo3/sysext/reports/Classes/Report/Status/SecurityStatus.php b/typo3/sysext/reports/Classes/Report/Status/SecurityStatus.php
index e7054aa3cd0..ece642e4bcb 100644
--- a/typo3/sysext/reports/Classes/Report/Status/SecurityStatus.php
+++ b/typo3/sysext/reports/Classes/Report/Status/SecurityStatus.php
@@ -23,10 +23,11 @@ use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Crypto\PasswordHashing\InvalidPasswordHashException;
 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;
 use TYPO3\CMS\Reports\Status as ReportStatus;
 
@@ -126,11 +127,11 @@ class SecurityStatus implements RequestAwareStatusProviderInterface
     {
         $value = $this->getLanguageService()->getLL('status_ok');
         $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');
         }
 

Vulnerable code:      * Sanitize HTTP_HOST, take proxy configuration into account and
     * verify allowed hosts with configured trusted hosts pattern.
     * @return string Sanitized HTTP_HOST
        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
            );
        }
    /**
     * State of host header value security check
     * in order to avoid unnecessary multiple checks during one request
     *
     * @var bool
     */
    protected static $allowHostHeaderValue = false;

                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
                    );
                }
     * 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.
        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);
        }
        return $hostMatch;
    /**
     * 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);
    }

        $textNewFirstPanelHeading = $I->grabTextFrom($versionPanel . ' .panel-heading');
        $I->assertNotEquals($textCurrentFirstPanelHeading, $textNewFirstPanelHeading);
    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;
    }

        GeneralUtilityFixture::$isAllowedHostHeaderValueCallCount = 0;
        GeneralUtilityFixture::setAllowHostHeaderValue(false);
        $GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL;
    /**
     * @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'));
    }

                'typo3/cms-frontend/timetracker',
    public function checkTrustedHostsPatternAction(): ResponseInterface
            'success' => GeneralUtility::hostHeaderValueMatchesTrustedHostsPattern($_SERVER['HTTP_HOST']),
    public function executeAdjustTrustedHostsPatternAction(): ResponseInterface
        if (!GeneralUtility::hostHeaderValueMatchesTrustedHostsPattern($_SERVER['HTTP_HOST'])) {
        if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {
            if (GeneralUtility::hostHeaderValueMatchesTrustedHostsPattern($_SERVER['HTTP_HOST'])) {
        if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === GeneralUtility::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {
Fixed code:         /** 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-core/verify-host-header',
            ],
     * Normalize HTTP_HOST by taking proxy configuration into account.
     * @return string Normalized HTTP_HOST
<?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';
    }
}
            Middleware\VerifyHostHeader::class => [ static::class, 'getVerifyHostHeaderMiddleware' ],
    public static function getVerifyHostHeaderMiddleware(ContainerInterface $container): Middleware\VerifyHostHeader
    {
        return self::new($container, Middleware\VerifyHostHeader::class, [
            $GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] ?? '',
        ]);
    }

use TYPO3\CMS\Core\Middleware\VerifyHostHeader;
    /* @deprecated since v11, will be removed in v12. */
    /* @deprecated since v11, will be removed in v12. */
     * @deprecated will be removed in TYPO3 v12.0.
        trigger_error('GeneralUtility::isAllowedHostHeaderValue() will be removed in TYPO3 v12.0. Host header is verified by frontend and backend middlewares.', E_USER_DEPRECATED);
        $verifyHostHeader = new VerifyHostHeader($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] ?? '');
        return $verifyHostHeader->isAllowedHostHeaderValue($hostHeaderValue, $_SERVER);
.. 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
        $I->dontSee($textCurrentFirstPanelHeading, '#version-1');
<?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());
    }
}
        '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/verify-host-header',
use TYPO3\CMS\Core\Middleware\VerifyHostHeader;
    /**
     * @var VerifyHostHeader
     */
    private $verifyHostHeader;

        VerifyHostHeader $verifyHostHeader,
        $this->verifyHostHeader = $verifyHostHeader;
     * @param ServerRequestInterface $request
    public function checkTrustedHostsPatternAction(ServerRequestInterface $request): ResponseInterface
        $serverParams = $request->getServerParams();
        $host = $serverParams['HTTP_HOST'] ?? '';

            'success' => $this->verifyHostHeader->isAllowedHostHeaderValue($host, $serverParams),
     * @param ServerRequestInterface $request
    public function executeAdjustTrustedHostsPatternAction(ServerRequestInterface $request): ResponseInterface
        $serverParams = $request->getServerParams();
        $host = $serverParams['HTTP_HOST'] ?? '';

        if (!$this->verifyHostHeader->isAllowedHostHeaderValue($host, $serverParams)) {
use TYPO3\CMS\Core\Middleware\VerifyHostHeader;
            $container->get(VerifyHostHeader::class),
use TYPO3\CMS\Core\Middleware\VerifyHostHeader;
        if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === VerifyHostHeader::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {
            $verifyHostHeader = new VerifyHostHeader($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] ?? '');
            if ($verifyHostHeader->isAllowedHostHeaderValue($_SERVER['HTTP_HOST'], $_SERVER)) {
    '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',
        ],
    ],
    'TYPO3\CMS\Core\Utility\GeneralUtility::isAllowedHostHeaderValue' => [
        'numberOfMandatoryArguments' => 1,
        'maximumNumberOfArguments' => 1,
        'restFiles' => [
            'Deprecation-95395-GeneralUtilityIsAllowedHostHeaderValueAndTrustedHostsPatternConstants.rst',
        ],
    ],
use TYPO3\CMS\Core\Middleware\VerifyHostHeader;
        if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] === VerifyHostHeader::ENV_TRUSTED_HOSTS_PATTERN_ALLOW_ALL) {