From 32310ff3aa8126ede47168fc9d9ae4a33b09c3a2 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 22 Oct 2024 10:31:42 +0200 Subject: [PATCH] [HttpFoundation] Reject URIs that contain invalid characters --- Request.php | 17 +++++++++++++++++ Tests/RequestTest.php | 30 ++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/Request.php b/Request.php index 561cb887f..e404b4cd0 100644 --- a/Request.php +++ b/Request.php @@ -11,6 +11,7 @@ namespace Symfony\Component\HttpFoundation; +use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; use Symfony\Component\HttpFoundation\Exception\JsonException; use Symfony\Component\HttpFoundation\Exception\SessionNotFoundException; @@ -333,6 +334,8 @@ public static function createFromGlobals() * @param string|resource|null $content The raw body data * * @return static + * + * @throws BadRequestException When the URI is invalid */ public static function create(string $uri, string $method = 'GET', array $parameters = [], array $cookies = [], array $files = [], array $server = [], $content = null) { @@ -360,6 +363,20 @@ public static function create(string $uri, string $method = 'GET', array $parame unset($components['fragment']); } + if (false === $components) { + throw new BadRequestException('Invalid URI.'); + } + + if (false !== ($i = strpos($uri, '\\')) && $i < strcspn($uri, '?#')) { + throw new BadRequestException('Invalid URI: A URI cannot contain a backslash.'); + } + if (\strlen($uri) !== strcspn($uri, "\r\n\t")) { + throw new BadRequestException('Invalid URI: A URI cannot contain CR/LF/TAB characters.'); + } + if ('' !== $uri && (\ord($uri[0]) <= 32 || \ord($uri[-1]) <= 32)) { + throw new BadRequestException('Invalid URI: A URI must not start nor end with ASCII control characters or spaces.'); + } + if (isset($components['host'])) { $server['SERVER_NAME'] = $components['host']; $server['HTTP_HOST'] = $components['host']; diff --git a/Tests/RequestTest.php b/Tests/RequestTest.php index 082e8695c..c2986907b 100644 --- a/Tests/RequestTest.php +++ b/Tests/RequestTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; +use Symfony\Component\HttpFoundation\Exception\BadRequestException; use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; use Symfony\Component\HttpFoundation\Exception\JsonException; use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException; @@ -289,9 +290,34 @@ public function testCreateWithRequestUri() $this->assertTrue($request->isSecure()); // Fragment should not be included in the URI - $request = Request::create('http://test.com/foo#bar'); - $request->server->set('REQUEST_URI', 'http://test.com/foo#bar'); + $request = Request::create('http://test.com/foo#bar\\baz'); + $request->server->set('REQUEST_URI', 'http://test.com/foo#bar\\baz'); $this->assertEquals('http://test.com/foo', $request->getUri()); + + $request = Request::create('http://test.com/foo?bar=f\\o'); + $this->assertEquals('http://test.com/foo?bar=f%5Co', $request->getUri()); + $this->assertEquals('/foo', $request->getPathInfo()); + $this->assertEquals('bar=f%5Co', $request->getQueryString()); + } + + /** + * @testWith ["http://foo.com\\bar"] + * ["\\\\foo.com/bar"] + * ["a\rb"] + * ["a\nb"] + * ["a\tb"] + * ["\u0000foo"] + * ["foo\u0000"] + * [" foo"] + * ["foo "] + * [":"] + */ + public function testCreateWithBadRequestUri(string $uri) + { + $this->expectException(BadRequestException::class); + $this->expectExceptionMessage('Invalid URI'); + + Request::create($uri); } /**