Skip to content

Commit 1884e99

Browse files
Merge branch '7.4' into 8.0
* 7.4: [Messenger] Fix commands writing to STDERR instead of STDOUT [HttpFoundation] Fix parsing hosts and schemes in URLs
2 parents edaa40b + a696cb0 commit 1884e99

File tree

2 files changed

+93
-14
lines changed

2 files changed

+93
-14
lines changed

Request.php

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -339,10 +339,21 @@ public static function create(string $uri, string $method = 'GET', array $parame
339339
$server['PATH_INFO'] = '';
340340
$server['REQUEST_METHOD'] = strtoupper($method);
341341

342+
if (($i = strcspn($uri, ':/?#')) && ':' === ($uri[$i] ?? null) && (strspn($uri, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-.') !== $i || strcspn($uri, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'))) {
343+
throw new BadRequestException('Invalid URI: Scheme is malformed.');
344+
}
342345
if (false === $components = parse_url(\strlen($uri) !== strcspn($uri, '?#') ? $uri : $uri.'#')) {
343346
throw new BadRequestException('Invalid URI.');
344347
}
345348

349+
$part = ($components['user'] ?? '').':'.($components['pass'] ?? '');
350+
351+
if (':' !== $part && \strlen($part) !== strcspn($part, '[]')) {
352+
throw new BadRequestException('Invalid URI: Userinfo is malformed.');
353+
}
354+
if (($part = $components['host'] ?? '') && !self::isHostValid($part)) {
355+
throw new BadRequestException('Invalid URI: Host is malformed.');
356+
}
346357
if (false !== ($i = strpos($uri, '\\')) && $i < strcspn($uri, '?#')) {
347358
throw new BadRequestException('Invalid URI: A URI cannot contain a backslash.');
348359
}
@@ -1129,10 +1140,8 @@ public function getHost(): string
11291140
// host is lowercase as per RFC 952/2181
11301141
$host = strtolower(preg_replace('/:\d+$/', '', trim($host)));
11311142

1132-
// as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user)
1133-
// check that it does not contain forbidden characters (see RFC 952 and RFC 2181)
1134-
// use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
1135-
if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) {
1143+
// the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user)
1144+
if ($host && !self::isHostValid($host)) {
11361145
if (!$this->isHostValid) {
11371146
return '';
11381147
}
@@ -2191,4 +2200,21 @@ private function isIisRewrite(): bool
21912200

21922201
return $this->isIisRewrite;
21932202
}
2203+
2204+
/**
2205+
* See https://url.spec.whatwg.org/.
2206+
*/
2207+
private static function isHostValid(string $host): bool
2208+
{
2209+
if ('[' === $host[0]) {
2210+
return ']' === $host[-1] && filter_var(substr($host, 1, -1), \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6);
2211+
}
2212+
2213+
if (preg_match('/\.[0-9]++\.?$/D', $host)) {
2214+
return null !== filter_var($host, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4 | \FILTER_NULL_ON_FAILURE);
2215+
}
2216+
2217+
// use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
2218+
return '' === preg_replace('/[-a-zA-Z0-9_]++\.?/', '', $host);
2219+
}
21942220
}

Tests/RequestTest.php

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2254,9 +2254,9 @@ public function createRequest(): Request
22542254
Request::setFactory(null);
22552255
}
22562256

2257-
#[DataProvider('getLongHostNames')]
2258-
public function testVeryLongHosts($host)
2257+
public function testVeryLongHosts()
22592258
{
2259+
$host = 'a'.str_repeat('.a', 40000);
22602260
$start = microtime(true);
22612261

22622262
$request = Request::create('/');
@@ -2297,14 +2297,6 @@ public static function getHostValidities()
22972297
];
22982298
}
22992299

2300-
public static function getLongHostNames()
2301-
{
2302-
return [
2303-
['a'.str_repeat('.a', 40000)],
2304-
[str_repeat(':', 101)],
2305-
];
2306-
}
2307-
23082300
#[DataProvider('methodIdempotentProvider')]
23092301
public function testMethodIdempotent($method, $idempotent)
23102302
{
@@ -2677,6 +2669,67 @@ public function testReservedFlags()
26772669
$this->assertNotSame(0b10000000, $value, \sprintf('The constant "%s" should not use the reserved value "0b10000000".', $constant));
26782670
}
26792671
}
2672+
2673+
#[DataProvider('provideMalformedUrls')]
2674+
public function testMalformedUrls(string $url, string $expectedException)
2675+
{
2676+
$this->expectException(BadRequestException::class);
2677+
$this->expectExceptionMessage($expectedException);
2678+
2679+
Request::create($url);
2680+
}
2681+
2682+
public static function provideMalformedUrls(): array
2683+
{
2684+
return [
2685+
['http://normal.com[@vulndetector.com/', 'Invalid URI: Userinfo is malformed.'],
2686+
['http://[[email protected]/', 'Invalid URI: Userinfo is malformed.'],
2687+
['http://normal.com@[vulndetector.com/', 'Invalid URI: Host is malformed.'],
2688+
['http://[[normal.com@][vulndetector.com/', 'Invalid URI: Userinfo is malformed.'],
2689+
['http://[vulndetector.com]', 'Invalid URI: Host is malformed.'],
2690+
['http://[0:0::vulndetector.com]:80', 'Invalid URI: Host is malformed.'],
2691+
['http://[2001:db8::vulndetector.com]', 'Invalid URI: Host is malformed.'],
2692+
['http://[malicious.com]', 'Invalid URI: Host is malformed.'],
2693+
['http://[evil.org]', 'Invalid URI: Host is malformed.'],
2694+
['http://[internal.server]', 'Invalid URI: Host is malformed.'],
2695+
['http://[192.168.1.1]', 'Invalid URI: Host is malformed.'],
2696+
['http://192.abc.1.1', 'Invalid URI: Host is malformed.'],
2697+
['http://[localhost]', 'Invalid URI: Host is malformed.'],
2698+
["\x80https://example.com", 'Invalid URI: Scheme is malformed.'],
2699+
['>https://example.com', 'Invalid URI: Scheme is malformed.'],
2700+
["http\x0b://example.com", 'Invalid URI: Scheme is malformed.'],
2701+
["https\x80://example.com", 'Invalid URI: Scheme is malformed.'],
2702+
['http>://example.com', 'Invalid URI: Scheme is malformed.'],
2703+
['0http://example.com', 'Invalid URI: Scheme is malformed.'],
2704+
];
2705+
}
2706+
2707+
#[DataProvider('provideLegitimateUrls')]
2708+
public function testLegitimateUrls(string $url)
2709+
{
2710+
$request = Request::create($url);
2711+
2712+
$this->assertInstanceOf(Request::class, $request);
2713+
}
2714+
2715+
public static function provideLegitimateUrls(): array
2716+
{
2717+
return [
2718+
['http://example.com'],
2719+
['https://example.com'],
2720+
['http://example.com:8080'],
2721+
['https://example.com:8443'],
2722+
['http://user:[email protected]'],
2723+
['http://user:[email protected]:8080'],
2724+
['http://user:[email protected]/path'],
2725+
['http://[2001:db8::1]'],
2726+
['http://[2001:db8::1]:8080'],
2727+
['http://[2001:db8::1]/path'],
2728+
['http://[::1]'],
2729+
['http://example.com/path'],
2730+
[':path'],
2731+
];
2732+
}
26802733
}
26812734

26822735
class RequestContentProxy extends Request

0 commit comments

Comments
 (0)