From 916bf8fd876cf2ece3751c9307fc67b0f88cd2b7 Mon Sep 17 00:00:00 2001
From: Benni Mack <benni@typo3.org>
Date: Tue, 6 Feb 2024 10:06:44 +0100
Subject: [PATCH] [BUGFIX] Properly resolve GET parameter `id`
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This change ensures that `?id=` only
validates against an integer-interpreted
value, disallowing `?id=nonsense23` which
previously resolved to "23"

In case an invalid `?id=` parameter value
is provided throw a page not found error.

Resolves: #103057
Releases: main, 12.4
Change-Id: Iaeb3096944b6872b73b056b690bf4f7fabbe9a6b
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/82867
Tested-by: Stefan Bürk <stefan@buerk.tech>
Tested-by: core-ci <typo3@b13.com>
Reviewed-by: Stefan Bürk <stefan@buerk.tech>
---
 .../core/Classes/Routing/PageRouter.php       | 10 ++--
 .../core/Classes/Routing/SiteMatcher.php      |  7 +++
 .../SiteHandling/SiteRequestTest.php          | 50 +++++++++++++++++++
 .../SiteHandling/SlugSiteRequestTest.php      | 50 +++++++++++++++++++
 4 files changed, 114 insertions(+), 3 deletions(-)

diff --git a/typo3/sysext/core/Classes/Routing/PageRouter.php b/typo3/sysext/core/Classes/Routing/PageRouter.php
index 3d61b4180b5a..347e6282054b 100644
--- a/typo3/sysext/core/Classes/Routing/PageRouter.php
+++ b/typo3/sysext/core/Classes/Routing/PageRouter.php
@@ -39,6 +39,7 @@ use TYPO3\CMS\Core\Routing\Enhancer\RoutingEnhancerInterface;
 use TYPO3\CMS\Core\Site\Entity\Site;
 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Frontend\Page\CacheHashCalculator;
 
 /**
@@ -104,9 +105,12 @@ class PageRouter implements RouterInterface
         $candidateProvider = $this->getSlugCandidateProvider($this->context);
 
         // Legacy URIs (?id=12345) takes precedence, no matter if a route is given
-        $requestId = (int)($request->getQueryParams()['id'] ?? 0);
-        if ($requestId > 0) {
-            if (!empty($pageId = $candidateProvider->getRealPageIdForPageIdAsPossibleCandidate($requestId))) {
+        $requestId = ($request->getQueryParams()['id'] ?? null);
+        if ($requestId !== null) {
+            if (MathUtility::canBeInterpretedAsInteger($requestId)
+                && (int)$requestId > 0
+                && !empty($pageId = $candidateProvider->getRealPageIdForPageIdAsPossibleCandidate((int)$requestId))
+            ) {
                 return new PageArguments(
                     (int)$pageId,
                     (string)($request->getQueryParams()['type'] ?? '0'),
diff --git a/typo3/sysext/core/Classes/Routing/SiteMatcher.php b/typo3/sysext/core/Classes/Routing/SiteMatcher.php
index 639080c0d3d6..36ce7e35c5d3 100644
--- a/typo3/sysext/core/Classes/Routing/SiteMatcher.php
+++ b/typo3/sysext/core/Classes/Routing/SiteMatcher.php
@@ -31,6 +31,7 @@ use TYPO3\CMS\Core\Site\Entity\SiteInterface;
 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Core\Utility\RootlineUtility;
 
 /**
@@ -185,6 +186,9 @@ class SiteMatcher implements SingletonInterface
         if ($pageId === null) {
             return null;
         }
+        if (!MathUtility::canBeInterpretedAsInteger($pageId)) {
+            return null;
+        }
         return (int)$pageId <= 0 ? null : (int)$pageId;
     }
 
@@ -197,6 +201,9 @@ class SiteMatcher implements SingletonInterface
         if ($languageId === null) {
             return null;
         }
+        if (!MathUtility::canBeInterpretedAsInteger($languageId)) {
+            return null;
+        }
         return (int)$languageId < 0 ? null : (int)$languageId;
     }
 
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/SiteRequestTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/SiteRequestTest.php
index 8d050f2d35ea..84c4ec6a8271 100644
--- a/typo3/sysext/frontend/Tests/Functional/SiteHandling/SiteRequestTest.php
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/SiteRequestTest.php
@@ -17,7 +17,10 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling;
 
+use Psr\Http\Message\UriInterface;
 use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Http\Uri;
+use TYPO3\CMS\Core\Utility\HttpUtility;
 use TYPO3\CMS\Core\Utility\PermutationUtility;
 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory;
 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter;
@@ -1057,4 +1060,51 @@ final class SiteRequestTest extends AbstractTestCase
             self::stringContains('ID was outside the domain')
         );
     }
+
+    public static function getUrisWithInvalidLegacyQueryParameters(): \Generator
+    {
+        $uri = new Uri('https://website.local/');
+        yield '#0 id with float value having a zero decimal' => [
+            'uri' => $uri->withQuery(HttpUtility::buildQueryString(['id' => '1110.0'])),
+        ];
+        yield '#1 id string value with tailing numbers' => [
+            'uri' => $uri->withQuery(HttpUtility::buildQueryString(['id' => 'step1110'])),
+        ];
+        yield '#2 id string value with leading numbers' => [
+            'uri' => $uri->withQuery(HttpUtility::buildQueryString(['id' => '1110step'])),
+        ];
+        yield '#3 id string value without numbers' => [
+            'uri' => $uri->withQuery(HttpUtility::buildQueryString(['id' => 'foobar'])),
+        ];
+        yield '#4 id string value with a exponent' => [
+            'uri' => $uri->withQuery(HttpUtility::buildQueryString(['id' => '11e10'])),
+        ];
+        yield '#5 id with a zero as value' => [
+            'uri' => $uri->withQuery(HttpUtility::buildQueryString(['id' => 0])),
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider getUrisWithInvalidLegacyQueryParameters
+     */
+    public function requestWithInvalidLegacyQueryParametersDisplayPageNotFoundPage(UriInterface $uri): void
+    {
+        $this->writeSiteConfiguration(
+            'website-local',
+            $this->buildSiteConfiguration(1000, 'https://website.local/'),
+            [],
+            $this->buildErrorHandlingConfiguration('PHP', [404])
+        );
+        $response = $this->executeFrontendSubRequest(
+            new InternalRequest((string)$uri),
+            new InternalRequestContext()
+        );
+        $json = json_decode((string)$response->getBody(), true);
+        self::assertSame(404, $response->getStatusCode());
+        self::assertThat(
+            $json['message'] ?? null,
+            self::stringContains('The requested page does not exist')
+        );
+    }
 }
diff --git a/typo3/sysext/frontend/Tests/Functional/SiteHandling/SlugSiteRequestTest.php b/typo3/sysext/frontend/Tests/Functional/SiteHandling/SlugSiteRequestTest.php
index 38a1cb4d2af8..32357b5cf688 100644
--- a/typo3/sysext/frontend/Tests/Functional/SiteHandling/SlugSiteRequestTest.php
+++ b/typo3/sysext/frontend/Tests/Functional/SiteHandling/SlugSiteRequestTest.php
@@ -17,7 +17,10 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling;
 
+use Psr\Http\Message\UriInterface;
 use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Http\Uri;
+use TYPO3\CMS\Core\Utility\HttpUtility;
 use TYPO3\CMS\Core\Utility\PermutationUtility;
 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory;
 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter;
@@ -2767,4 +2770,51 @@ final class SlugSiteRequestTest extends AbstractTestCase
             );
         }
     }
+
+    public static function getUrisWithInvalidLegacyQueryParameters(): \Generator
+    {
+        $uri = new Uri('https://website.local/welcome/');
+        yield '#0 id with float value having a zero decimal' => [
+            'uri' => $uri->withQuery(HttpUtility::buildQueryString(['id' => '1110.0'])),
+        ];
+        yield '#1 id string value with tailing numbers' => [
+            'uri' => $uri->withQuery(HttpUtility::buildQueryString(['id' => 'step1110'])),
+        ];
+        yield '#2 id string value with leading numbers' => [
+            'uri' => $uri->withQuery(HttpUtility::buildQueryString(['id' => '1110step'])),
+        ];
+        yield '#3 id string value without numbers' => [
+            'uri' => $uri->withQuery(HttpUtility::buildQueryString(['id' => 'foobar'])),
+        ];
+        yield '#4 id string value with a exponent' => [
+            'uri' => $uri->withQuery(HttpUtility::buildQueryString(['id' => '11e10'])),
+        ];
+        yield '#5 id with a zero as value' => [
+            'uri' => $uri->withQuery(HttpUtility::buildQueryString(['id' => 0])),
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider getUrisWithInvalidLegacyQueryParameters
+     */
+    public function requestWithInvalidLegacyQueryParametersDisplayPageNotFoundPage(UriInterface $uri): void
+    {
+        $this->writeSiteConfiguration(
+            'website-local',
+            $this->buildSiteConfiguration(1000, 'https://website.local/'),
+            [],
+            $this->buildErrorHandlingConfiguration('PHP', [404])
+        );
+        $response = $this->executeFrontendSubRequest(
+            new InternalRequest((string)$uri),
+            new InternalRequestContext()
+        );
+        $json = json_decode((string)$response->getBody(), true);
+        self::assertSame(404, $response->getStatusCode());
+        self::assertThat(
+            $json['message'] ?? null,
+            self::stringContains('The requested page does not exist')
+        );
+    }
 }
-- 
GitLab