From f3c62437431a7fd35e02c332644e1d06649f954a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Fri, 1 Jul 2022 15:23:32 +0200 Subject: [PATCH] Ignore obviously malformed `host` headers when constructing a ServerRequest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I opted to ignore the `host` header instead of throwing an Exception to not introduce a remotely triggerable exception. Signed-off-by: Tim Düsterhus --- src/ServerRequestFactory.php | 10 +++++++- test/ServerRequestFactoryTest.php | 38 +++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/ServerRequestFactory.php b/src/ServerRequestFactory.php index 6f8dec0a..c35813f7 100644 --- a/src/ServerRequestFactory.php +++ b/src/ServerRequestFactory.php @@ -168,7 +168,15 @@ private static function marshalHostAndPort(array $server, array $headers) : arra $host = self::getHeaderFromArray('host', $headers, false); if ($host !== false) { - return self::marshalHostAndPortFromHeader($host); + // Ignore obviously malformed host headers: + // - Whitespace is invalid within a hostname and break the URI representation within HTTP. + // non-printable characters other than SPACE and TAB are already rejected by HeaderSecurity. + // - A comma indicates that multiple host headers have been sent which is not legal + // and might be used in an attack where a load balancer sees a different host header + // than Diactoros. + if (! \preg_match('/[\\s,]/', $host)) { + return self::marshalHostAndPortFromHeader($host); + } } if (! isset($server['SERVER_NAME'])) { diff --git a/test/ServerRequestFactoryTest.php b/test/ServerRequestFactoryTest.php index eeda1682..c926d4cd 100644 --- a/test/ServerRequestFactoryTest.php +++ b/test/ServerRequestFactoryTest.php @@ -786,4 +786,42 @@ public function testHonorsHostHeaderOverServerNameWhenMarshalingUrl(): void $uri = $request->getUri(); $this->assertSame('example.com', $uri->getHost()); } + + /** + * @psalm-return iterable + */ + public function invalidHostHeaders(): iterable + { + return [ + 'comma' => ['example.com,example.net'], + 'space' => ['example com'], + 'tab' => ["example\tcom"], + 'lf' => ["example\ncom"], + 'cr' => ["example\rcom"], + ]; + } + + /** + * @dataProvider invalidHostHeaders + */ + public function testRejectsDuplicatedHostHeader(string $host): void + { + $server = [ + 'HTTP_HOST' => $host, + ]; + + $request = ServerRequestFactory::fromGlobals( + $server, + null, + null, + null, + null, + new DoNotFilter() + ); + + $uri = $request->getUri(); + $this->assertSame('', $uri->getHost()); + } }