From 840f8af3eaf4fcc9ad9c0d36d261564601a59b9b Mon Sep 17 00:00:00 2001
From: Benni Mack <benni@typo3.org>
Date: Fri, 23 Dec 2022 12:19:05 +0100
Subject: [PATCH] [BUGFIX] Enforce validation when no cHash is given

When no cHash is given but GET parameters are handed in
which _would_ require cHash parameters, these are now
properly evaluated during the frontend request.

As this has a security impact,
a new option called
$GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['enforceValidation']
is introduced, which then skips
the "requireCacheHashPresenceParameters" option.
The latter is an include list, but cache Hash
calculation should rather be based on
the exclude list such as "excludedParameters" and
"cachedParametersWhiteList".

If the new option is set, but some properties such
as tx_solr[q] should be allowed, then this needs
to be added to the excludedList ("excludedParameters")
by extension authors.

A new test "SlugSiteWithoutRequiredCHashRequestTest"
is added which works with a disabled feature
flag compared to "SlugSiteRequestTest" which
has the feature flag enabled.

Resolves: #95297
Releases: main, 11.5, 10.4
Change-Id: Ib72c6a34602e77d8c2044ad2e826c0474ebd2326
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/77712
Tested-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: core-ci <typo3@b13.com>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
---
 .../Configuration/DefaultConfiguration.php    |   1 +
 .../DefaultConfigurationDescription.yaml      |   3 +
 .../Configuration/FactoryConfiguration.php    |   3 +
 ...95297-StrictCHashValidationFeatureFlag.rst |  41 +++++
 .../Functional/View/TemplatesPathsTest.php    |   5 +
 .../Link/TypolinkViewHelperTest.php           |   8 +
 .../Uri/TypolinkViewHelperTest.php            |   8 +
 .../Middleware/PageArgumentValidator.php      |  26 ++-
 .../Tests/Functional/Fixtures/be_users.csv    |   4 +
 .../Rendering/TitleTagRenderingTest.php       |   5 +
 .../Rendering/UriPrefixRenderingTest.php      |   5 +
 .../SiteHandling/AbstractTestCase.php         |   4 +-
 .../SiteHandling/SlugSiteRequestTest.php      |   1 +
 ...lugSiteWithoutRequiredCHashRequestTest.php | 167 ++++++++++++++++++
 .../Service/RedirectServiceTest.php           |  13 ++
 .../XmlSitemap/XmlSitemapPagesTest.php        |  11 ++
 ...itemapPagesWithHideIfNotTranslatedTest.php |   3 +
 .../XmlSitemap/XmlSitemapRecordsTest.php      |  11 ++
 .../XmlSitemap/XmlSitemapXslTest.php          |  19 +-
 19 files changed, 335 insertions(+), 3 deletions(-)
 create mode 100644 typo3/sysext/core/Documentation/Changelog/10.4.x/Important-95297-StrictCHashValidationFeatureFlag.rst
 create mode 100644 typo3/sysext/frontend/Tests/Functional/Fixtures/be_users.csv
 create mode 100644 typo3/sysext/frontend/Tests/Functional/SiteHandling/SlugSiteWithoutRequiredCHashRequestTest.php

diff --git a/typo3/sysext/core/Configuration/DefaultConfiguration.php b/typo3/sysext/core/Configuration/DefaultConfiguration.php
index 515d97820007..ca610a17af3f 100644
--- a/typo3/sysext/core/Configuration/DefaultConfiguration.php
+++ b/typo3/sysext/core/Configuration/DefaultConfiguration.php
@@ -1444,6 +1444,7 @@ return [
             'requireCacheHashPresenceParameters' => [],
             'excludeAllEmptyParameters' => false,
             'excludedParametersIfEmpty' => [],
+            'enforceValidation' => false,
         ],
         'additionalCanonicalizedUrlParameters' => [],
         'workspacePreviewLogoutTemplate' => '',
diff --git a/typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml b/typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml
index b4cd496e7acf..47ed70cda67b 100644
--- a/typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml
+++ b/typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml
@@ -541,6 +541,9 @@ FE:
               excludeAllEmptyParameters:
                   type: bool
                   description: 'If true, all parameters which are relevant for cHash are only considered if they are non-empty.'
+              enforceValidation:
+                  type: bool
+                  description: 'If true, a cHash parameter is always required if a query parameter is included in the query string of the incoming request, unless the query argument is included in the excludedParameters list.'
         additionalCanonicalizedUrlParameters:
             type: array
             description: The given parameters will be included when calculating canonicalized URL
diff --git a/typo3/sysext/core/Configuration/FactoryConfiguration.php b/typo3/sysext/core/Configuration/FactoryConfiguration.php
index 5f180d700ce1..6510537e1a7d 100644
--- a/typo3/sysext/core/Configuration/FactoryConfiguration.php
+++ b/typo3/sysext/core/Configuration/FactoryConfiguration.php
@@ -19,6 +19,9 @@ return [
     ],
     'FE' => [
         'disableNoCacheParameter' => true,
+        'cacheHash' => [
+            'enforceValidation' => true,
+        ],
     ],
     'SYS' => [
         'sitename' => 'New TYPO3 site',
diff --git a/typo3/sysext/core/Documentation/Changelog/10.4.x/Important-95297-StrictCHashValidationFeatureFlag.rst b/typo3/sysext/core/Documentation/Changelog/10.4.x/Important-95297-StrictCHashValidationFeatureFlag.rst
new file mode 100644
index 000000000000..f9db46128da9
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/10.4.x/Important-95297-StrictCHashValidationFeatureFlag.rst
@@ -0,0 +1,41 @@
+.. include:: /Includes.rst.txt
+
+.. _important-95297-1674809371:
+
+========================================================
+Important: #95297 - Strict cHash validation feature flag
+========================================================
+
+See :issue:`95297`
+
+Description
+===========
+
+Since TYPO3 v9 and the PSR-15 Middleware concept, cHash validation was moved
+outside of plugins and rendering code inside a validation middleware to check if
+a given "cHash" acts as a signature of other query parameters in order to use a
+cached version of a frontend page.
+
+However, the check only provided information about an invalid "cHash" in the
+query parameters. When no "cHash" was given, the only option was to add an
+"required list" (global TYPO3 configuration option
+`requireCacheHashPresenceParameters`), but not based on the a final
+`excludedParameters` for cache hash calculation of given query parameters.
+
+For this reason, a new global TYPO3 configuration option
+:php:`$GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['enforceValidation']`
+has been added.
+
+When enabled, the same validation for calculating a "cHash" value is used as
+when a valid or invalid "cHash" parameter is given to a request, even when no
+"cHash" is given.
+
+The new option is disabled for existing installations, but enabled for new
+installations. It is also highly recommended to enable this option in
+your existing installations.
+
+In future TYPO3 versions, this functionality will be enabled for all TYPO3
+installations, while the configuration option
+`requireCacheHashPresenceParameters` will be removed.
+
+.. index:: Frontend, LocalConfiguration, ext:frontend
diff --git a/typo3/sysext/fluid/Tests/Functional/View/TemplatesPathsTest.php b/typo3/sysext/fluid/Tests/Functional/View/TemplatesPathsTest.php
index ba5c34e64990..f99d7d278d9c 100644
--- a/typo3/sysext/fluid/Tests/Functional/View/TemplatesPathsTest.php
+++ b/typo3/sysext/fluid/Tests/Functional/View/TemplatesPathsTest.php
@@ -62,6 +62,11 @@ class TemplatesPathsTest extends FunctionalTestCase
                 ],
             ],
         ],
+        'FE' => [
+            'cacheHash' => [
+                'enforceValidation' => false,
+            ],
+        ],
     ];
 
     public function setUp(): void
diff --git a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Link/TypolinkViewHelperTest.php b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Link/TypolinkViewHelperTest.php
index 95e7eed155db..db5be4d203f7 100644
--- a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Link/TypolinkViewHelperTest.php
+++ b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Link/TypolinkViewHelperTest.php
@@ -34,6 +34,14 @@ class TypolinkViewHelperTest extends FunctionalTestCase
         'EN' => ['id' => 0, 'title' => 'English', 'locale' => 'en_US.UTF8'],
     ];
 
+    protected $configurationToUseInTestInstance = [
+        'FE' => [
+            'cacheHash' => [
+                'enforceValidation' => false,
+            ],
+        ],
+    ];
+
     public function renderDataProvider(): array
     {
         return [
diff --git a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Uri/TypolinkViewHelperTest.php b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Uri/TypolinkViewHelperTest.php
index 60881332dbdc..791c7f6304ba 100644
--- a/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Uri/TypolinkViewHelperTest.php
+++ b/typo3/sysext/fluid/Tests/Functional/ViewHelpers/Uri/TypolinkViewHelperTest.php
@@ -33,6 +33,14 @@ class TypolinkViewHelperTest extends FunctionalTestCase
         'EN' => ['id' => 0, 'title' => 'English', 'locale' => 'en_US.UTF8'],
     ];
 
+    protected $configurationToUseInTestInstance = [
+        'FE' => [
+            'cacheHash' => [
+                'enforceValidation' => false,
+            ],
+        ],
+    ];
+
     protected function setUp(): void
     {
         parent::setUp();
diff --git a/typo3/sysext/frontend/Classes/Middleware/PageArgumentValidator.php b/typo3/sysext/frontend/Classes/Middleware/PageArgumentValidator.php
index bd3fd0632451..296f2b789695 100644
--- a/typo3/sysext/frontend/Classes/Middleware/PageArgumentValidator.php
+++ b/typo3/sysext/frontend/Classes/Middleware/PageArgumentValidator.php
@@ -119,7 +119,7 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
                     );
                 }
             // No cHash given but was required
-            } elseif (!$this->evaluateQueryParametersWithoutCacheHash($queryParams, $pageNotFoundOnValidationError)) {
+            } elseif (!$this->evaluatePageArgumentsWithoutCacheHash($pageArguments, $pageNotFoundOnValidationError)) {
                 return GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
                     $request,
                     'Request parameters could not be validated (&cHash empty)',
@@ -193,4 +193,28 @@ class PageArgumentValidator implements MiddlewareInterface, LoggerAwareInterface
         $this->timeTracker->setTSlogMessage('TSFE->reqCHash(): No &cHash parameter was sent for GET vars though required so caching is disabled', LogLevel::ERROR);
         return true;
     }
+
+    /**
+     * No cHash is set but there are query parameters, then calculate a possible cHash from the given
+     * query parameters and see if a cHash is returned (similar to comparing this).
+     *
+     * Is only called if NO cHash parameter is given.
+     */
+    protected function evaluatePageArgumentsWithoutCacheHash(PageArguments $pageArguments, bool $pageNotFoundOnCacheHashError): bool
+    {
+        // legacy behaviour
+        if (!($GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['enforceValidation'] ?? false)) {
+            return $this->evaluateQueryParametersWithoutCacheHash($pageArguments->getDynamicArguments(), $pageNotFoundOnCacheHashError);
+        }
+        $relevantParameters = $this->getRelevantParametersForCacheHashCalculation($pageArguments);
+        // There are parameters that would be needed for the current page, but no cHash is given.
+        // Thus, a "page not found" error is thrown - as configured via "pageNotFoundOnCHashError".
+        if (!empty($relevantParameters) && $pageNotFoundOnCacheHashError) {
+            return false;
+        }
+        // Caching is disabled now (but no 404)
+        $this->disableCache = true;
+        $this->timeTracker->setTSlogMessage('No &cHash parameter was sent for given query parameters, so caching is disabled', LogLevel::ERROR);
+        return true;
+    }
 }
diff --git a/typo3/sysext/frontend/Tests/Functional/Fixtures/be_users.csv b/typo3/sysext/frontend/Tests/Functional/Fixtures/be_users.csv
new file mode 100644
index 000000000000..a8a129e2808d
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/Fixtures/be_users.csv
@@ -0,0 +1,4 @@
+"be_users"
+,"uid","pid","tstamp","username","password","admin","disable","starttime","endtime","options","crdate","workspace_perms","deleted","TSconfig","lastlogin","workspace_id"
+# The password is "password"
+,1,0,1366642540,"admin","$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1",1,0,0,0,0,1366642540,1,0,,1371033743,0
diff --git a/typo3/sysext/frontend/Tests/Functional/Rendering/TitleTagRenderingTest.php b/typo3/sysext/frontend/Tests/Functional/Rendering/TitleTagRenderingTest.php
index ebb5d52eaa64..9751f7473243 100644
--- a/typo3/sysext/frontend/Tests/Functional/Rendering/TitleTagRenderingTest.php
+++ b/typo3/sysext/frontend/Tests/Functional/Rendering/TitleTagRenderingTest.php
@@ -37,6 +37,11 @@ class TitleTagRenderingTest extends FunctionalTestCase
                 ],
             ],
         ],
+        'FE' => [
+            'cacheHash' => [
+                'enforceValidation' => false,
+            ],
+        ],
     ];
 
     protected function setUp(): void
diff --git a/typo3/sysext/frontend/Tests/Functional/Rendering/UriPrefixRenderingTest.php b/typo3/sysext/frontend/Tests/Functional/Rendering/UriPrefixRenderingTest.php
index 50ab923a44fe..93494b907636 100644
--- a/typo3/sysext/frontend/Tests/Functional/Rendering/UriPrefixRenderingTest.php
+++ b/typo3/sysext/frontend/Tests/Functional/Rendering/UriPrefixRenderingTest.php
@@ -58,6 +58,11 @@ class UriPrefixRenderingTest extends FunctionalTestCase
     protected $coreExtensionsToLoad = ['rte_ckeditor'];
 
     protected $configurationToUseInTestInstance = [
+        'FE' => [
+            'cacheHash' => [
+                'excludedParameters' => ['testAbsRefPrefix', 'testCompressor'],
+            ],
+        ],
         'SC_OPTIONS' => [
             'Core/TypoScript/TemplateService' => [
                 'runThroughTemplatesPostProcessing' => [
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/AbstractTestCase.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/AbstractTestCase.php
index 8f78b03d884c..f8efe2ae861f 100644
--- a/typo3/sysext/frontend/Tests/Functional/SiteHandling/AbstractTestCase.php
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/AbstractTestCase.php
@@ -45,7 +45,9 @@ abstract class AbstractTestCase extends FunctionalTestCase
         'FE' => [
             'cacheHash' => [
                 'requireCacheHashPresenceParameters' => ['value', 'testing[value]', 'tx_testing_link[value]'],
-                'excludedParameters' => ['tx_testing_link[excludedValue]'],
+                'excludedParameters' => ['L', 'tx_testing_link[excludedValue]'],
+                // @todo this should be tested explicitly - enabled and disabled
+                'enforceValidation' => false,
             ],
             'debug' => false,
         ],
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/SlugSiteRequestTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/SlugSiteRequestTest.php
index 42ee135f4fb3..e81e5652a399 100644
--- a/typo3/sysext/frontend/Tests/Functional/SiteHandling/SlugSiteRequestTest.php
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/SlugSiteRequestTest.php
@@ -42,6 +42,7 @@ class SlugSiteRequestTest extends AbstractTestCase
             'cacheHash' => [
                 'requireCacheHashPresenceParameters' => ['value', 'testing[value]', 'tx_testing_link[value]'],
                 'excludedParameters' => ['tx_testing_link[excludedValue]'],
+                'enforceValidation' => true,
             ],
             'debug' => false,
         ],
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/SlugSiteWithoutRequiredCHashRequestTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/SlugSiteWithoutRequiredCHashRequestTest.php
new file mode 100644
index 000000000000..7d1cead18167
--- /dev/null
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/SlugSiteWithoutRequiredCHashRequestTest.php
@@ -0,0 +1,167 @@
+<?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\Frontend\Tests\Functional\SiteHandling;
+
+use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Utility\PermutationUtility;
+use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory;
+use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\ResponseContent;
+
+class SlugSiteWithoutRequiredCHashRequestTest extends AbstractTestCase
+{
+    /**
+     * @var array<string, mixed>
+     */
+    protected $configurationToUseInTestInstance = [
+        'SYS' => [
+            'devIPmask' => '123.123.123.123',
+            'encryptionKey' => '4408d27a916d51e624b69af3554f516dbab61037a9f7b9fd6f81b4d3bedeccb6',
+        ],
+        'FE' => [
+            'cacheHash' => [
+                'enforceValidation' => false,
+            ],
+            'debug' => false,
+        ],
+    ];
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->withDatabaseSnapshot(function () {
+            $this->importCSVDataSet(__DIR__ . '/../Fixtures/be_users.csv');
+            $backendUser = $this->setUpBackendUser(1);
+            Bootstrap::initializeLanguageObject();
+            $scenarioFile = __DIR__ . '/Fixtures/SlugScenario.yaml';
+            $factory = DataHandlerFactory::fromYamlFile($scenarioFile);
+            $writer = DataHandlerWriter::withBackendUser($backendUser);
+            $writer->invokeFactory($factory);
+            static::failIfArrayIsNotEmpty($writer->getErrors());
+            $this->setUpFrontendRootPage(
+                1000,
+                [
+                    'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/JsonRenderer.typoscript',
+                    'typo3/sysext/frontend/Tests/Functional/SiteHandling/Fixtures/JsonRenderer.typoscript',
+                ],
+                [
+                    'title' => 'ACME Root',
+                ]
+            );
+        });
+    }
+
+    public function pageRenderingStopsWithInvalidCacheHashDataProvider(): array
+    {
+        $domainPaths = [
+            'https://website.local/',
+        ];
+
+        $queries = [
+            '',
+            'welcome',
+        ];
+
+        $customQueries = [
+            '?testing[value]=1',
+            '?testing[value]=1&cHash=',
+            '?testing[value]=1&cHash=WRONG',
+        ];
+
+        return $this->wrapInArray(
+            $this->keysFromValues(
+                PermutationUtility::meltStringItems([$domainPaths, $queries, $customQueries])
+            )
+        );
+    }
+
+    /**
+     * @test
+     * @dataProvider pageRenderingStopsWithInvalidCacheHashDataProvider
+     */
+    public function pageRequestSendsNotFoundResponseWithInvalidCacheHash(string $uri): void
+    {
+        $this->writeSiteConfiguration(
+            'website-local',
+            $this->buildSiteConfiguration(1000, 'https://website.local/'),
+            [],
+            $this->buildErrorHandlingConfiguration('PHP', [404])
+        );
+
+        $response = $this->executeFrontendSubRequest(new InternalRequest($uri));
+        $json = json_decode((string)$response->getBody(), true);
+
+        self::assertThat(
+            $json['message'] ?? null,
+            self::logicalOr(
+                self::identicalTo(null),
+                self::identicalTo('Request parameters could not be validated (&cHash comparison failed)')
+            )
+        );
+    }
+
+    public function pageIsRenderedWithValidCacheHashDataProvider(): array
+    {
+        $domainPaths = [
+            'https://website.local/',
+        ];
+
+        // cHash has been calculated with encryption key set to
+        // '4408d27a916d51e624b69af3554f516dbab61037a9f7b9fd6f81b4d3bedeccb6'
+        $queries = [
+            // @todo Currently fails since cHash is verified after(!) redirect to page 1100
+            // '?cHash=7d1f13fa91159dac7feb3c824936b39d',
+            // '?cHash=7d1f13fa91159dac7feb3c824936b39d',
+            'welcome?cHash=f42b850e435f0cedd366f5db749fc1af',
+        ];
+
+        $customQueries = [
+            '&testing[value]=1',
+        ];
+
+        $dataSet = $this->wrapInArray(
+            $this->keysFromValues(
+                PermutationUtility::meltStringItems([$domainPaths, $queries, $customQueries])
+            )
+        );
+
+        return $dataSet;
+    }
+
+    /**
+     * @test
+     * @dataProvider pageIsRenderedWithValidCacheHashDataProvider
+     */
+    public function pageIsRenderedWithValidCacheHash($uri): void
+    {
+        $this->writeSiteConfiguration(
+            'website-local',
+            $this->buildSiteConfiguration(1000, 'https://website.local/')
+        );
+
+        $response = $this->executeFrontendSubRequest(new InternalRequest($uri));
+        $responseStructure = ResponseContent::fromString(
+            (string)$response->getBody()
+        );
+        self::assertSame(
+            '1',
+            $responseStructure->getScopePath('getpost/testing.value')
+        );
+    }
+}
diff --git a/typo3/sysext/redirects/Tests/Functional/Service/RedirectServiceTest.php b/typo3/sysext/redirects/Tests/Functional/Service/RedirectServiceTest.php
index edec2c3e5d71..001e5372f580 100644
--- a/typo3/sysext/redirects/Tests/Functional/Service/RedirectServiceTest.php
+++ b/typo3/sysext/redirects/Tests/Functional/Service/RedirectServiceTest.php
@@ -44,6 +44,19 @@ class RedirectServiceTest extends FunctionalTestCase
 
     protected array $testFilesToDelete = [];
 
+    /**
+     * @var array<string, mixed>
+     */
+    protected $configurationToUseInTestInstance = [
+        'FE' => [
+            'cacheHash' => [
+                'excludedParameters' => ['L', 'pk_campaign', 'pk_kwd', 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'gclid', 'fbclid', 'msclkid'],
+                // @todo this should be tested explicitly - enabled and disabled
+                'enforceValidation' => false,
+            ],
+        ],
+    ];
+
     protected function setUp(): void
     {
         parent::setUp();
diff --git a/typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapPagesTest.php b/typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapPagesTest.php
index 0d75d70eb646..bc7b48e3f725 100644
--- a/typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapPagesTest.php
+++ b/typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapPagesTest.php
@@ -22,6 +22,17 @@ namespace TYPO3\CMS\Seo\Tests\Functional\XmlSitemap;
  */
 class XmlSitemapPagesTest extends AbstractXmlSitemapPagesTest
 {
+    /**
+     * @var array<string, mixed>
+     */
+    protected $configurationToUseInTestInstance = [
+        'FE' => [
+            'cacheHash' => [
+                'enforceValidation' => false,
+            ],
+        ],
+    ];
+
     /**
      * @param string $urlPattern
      * @test
diff --git a/typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapPagesWithHideIfNotTranslatedTest.php b/typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapPagesWithHideIfNotTranslatedTest.php
index 436f61584e33..7a01f0d50ae1 100644
--- a/typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapPagesWithHideIfNotTranslatedTest.php
+++ b/typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapPagesWithHideIfNotTranslatedTest.php
@@ -31,6 +31,9 @@ class XmlSitemapPagesWithHideIfNotTranslatedTest extends AbstractXmlSitemapPages
     protected $configurationToUseInTestInstance = [
         'FE' => [
             'hidePagesIfNotTranslatedByDefault' => true,
+            'cacheHash' => [
+                'enforceValidation' => false,
+            ],
         ],
     ];
 
diff --git a/typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapRecordsTest.php b/typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapRecordsTest.php
index c08829b6ba72..dd39fd4adab7 100644
--- a/typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapRecordsTest.php
+++ b/typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapRecordsTest.php
@@ -27,6 +27,17 @@ class XmlSitemapRecordsTest extends AbstractTestCase
 {
     protected $coreExtensionsToLoad = ['seo'];
 
+    /**
+     * @var array<string, mixed>
+     */
+    protected $configurationToUseInTestInstance = [
+        'FE' => [
+            'cacheHash' => [
+                'enforceValidation' => false,
+            ],
+        ],
+    ];
+
     protected function setUp(): void
     {
         parent::setUp();
diff --git a/typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapXslTest.php b/typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapXslTest.php
index 9281c6bfa765..3449c455e672 100644
--- a/typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapXslTest.php
+++ b/typo3/sysext/seo/Tests/Functional/XmlSitemap/XmlSitemapXslTest.php
@@ -24,6 +24,23 @@ class XmlSitemapXslTest extends AbstractTestCase
 {
     protected $coreExtensionsToLoad = ['seo'];
 
+    /**
+     * @var array<string, mixed>
+     */
+    protected $configurationToUseInTestInstance = [
+        'SYS' => [
+            'encryptionKey' => '4408d27a916d51e624b69af3554f516dbab61037a9f7b9fd6f81b4d3bedeccb6',
+        ],
+        'FE' => [
+            'cacheHash' => [
+                'requireCacheHashPresenceParameters' => ['value', 'testing[value]', 'tx_testing_link[value]'],
+                'excludedParameters' => ['tx_testing_link[excludedValue]'],
+                'enforceValidation' => false,
+            ],
+            'debug' => false,
+        ],
+    ];
+
     protected function setUp(): void
     {
         parent::setUp();
@@ -34,7 +51,7 @@ class XmlSitemapXslTest extends AbstractTestCase
      * @test
      * @dataProvider getXslFilePathsDataProvider
      */
-    public function checkIfDefaultSitemapReturnsDefaultXsl($typoscriptSetupFiles, $sitemap, $xslFilePath): void
+    public function checkIfDefaultSitemapReturnsDefaultXsl(array $typoscriptSetupFiles, string $sitemap, string $xslFilePath): void
     {
         $this->setUpFrontendRootPage(
             1,
-- 
GitLab