Skip to content

Commit 6379e49

Browse files
Merge branch '6.4' into 7.3
* 6.4: [Messenger] Fix commands writing to STDERR instead of STDOUT [HttpFoundation] Fix parsing hosts and schemes in URLs
2 parents c8ab9b6 + 1ba1d5f commit 6379e49

File tree

2 files changed

+97
-16
lines changed

2 files changed

+97
-16
lines changed

Request.php

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

303+
if (($i = strcspn($uri, ':/?#')) && ':' === ($uri[$i] ?? null) && (strspn($uri, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-.') !== $i || strcspn($uri, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'))) {
304+
throw new BadRequestException('Invalid URI: Scheme is malformed.');
305+
}
303306
if (false === $components = parse_url(\strlen($uri) !== strcspn($uri, '?#') ? $uri : $uri.'#')) {
304307
throw new BadRequestException('Invalid URI.');
305308
}
306309

310+
$part = ($components['user'] ?? '').':'.($components['pass'] ?? '');
311+
312+
if (':' !== $part && \strlen($part) !== strcspn($part, '[]')) {
313+
throw new BadRequestException('Invalid URI: Userinfo is malformed.');
314+
}
315+
if (($part = $components['host'] ?? '') && !self::isHostValid($part)) {
316+
throw new BadRequestException('Invalid URI: Host is malformed.');
317+
}
307318
if (false !== ($i = strpos($uri, '\\')) && $i < strcspn($uri, '?#')) {
308319
throw new BadRequestException('Invalid URI: A URI cannot contain a backslash.');
309320
}
@@ -1091,10 +1102,8 @@ public function getHost(): string
10911102
// host is lowercase as per RFC 952/2181
10921103
$host = strtolower(preg_replace('/:\d+$/', '', trim($host)));
10931104

1094-
// as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user)
1095-
// check that it does not contain forbidden characters (see RFC 952 and RFC 2181)
1096-
// use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
1097-
if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) {
1105+
// the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user)
1106+
if ($host && !self::isHostValid($host)) {
10981107
if (!$this->isHostValid) {
10991108
return '';
11001109
}
@@ -2108,4 +2117,21 @@ private function isIisRewrite(): bool
21082117

21092118
return $this->isIisRewrite;
21102119
}
2120+
2121+
/**
2122+
* See https://url.spec.whatwg.org/.
2123+
*/
2124+
private static function isHostValid(string $host): bool
2125+
{
2126+
if ('[' === $host[0]) {
2127+
return ']' === $host[-1] && filter_var(substr($host, 1, -1), \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6);
2128+
}
2129+
2130+
if (preg_match('/\.[0-9]++\.?$/D', $host)) {
2131+
return null !== filter_var($host, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4 | \FILTER_NULL_ON_FAILURE);
2132+
}
2133+
2134+
// use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
2135+
return '' === preg_replace('/[-a-zA-Z0-9_]++\.?/', '', $host);
2136+
}
21112137
}

Tests/RequestTest.php

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2252,11 +2252,9 @@ public function createRequest(): Request
22522252
Request::setFactory(null);
22532253
}
22542254

2255-
/**
2256-
* @dataProvider getLongHostNames
2257-
*/
2258-
public function testVeryLongHosts($host)
2255+
public function testVeryLongHosts()
22592256
{
2257+
$host = 'a'.str_repeat('.a', 40000);
22602258
$start = microtime(true);
22612259

22622260
$request = Request::create('/');
@@ -2299,14 +2297,6 @@ public static function getHostValidities()
22992297
];
23002298
}
23012299

2302-
public static function getLongHostNames()
2303-
{
2304-
return [
2305-
['a'.str_repeat('.a', 40000)],
2306-
[str_repeat(':', 101)],
2307-
];
2308-
}
2309-
23102300
/**
23112301
* @dataProvider methodIdempotentProvider
23122302
*/
@@ -2692,6 +2682,71 @@ public function testReservedFlags()
26922682
$this->assertNotSame(0b10000000, $value, \sprintf('The constant "%s" should not use the reserved value "0b10000000".', $constant));
26932683
}
26942684
}
2685+
2686+
/**
2687+
* @dataProvider provideMalformedUrls
2688+
*/
2689+
public function testMalformedUrls(string $url, string $expectedException)
2690+
{
2691+
$this->expectException(BadRequestException::class);
2692+
$this->expectExceptionMessage($expectedException);
2693+
2694+
Request::create($url);
2695+
}
2696+
2697+
public static function provideMalformedUrls(): array
2698+
{
2699+
return [
2700+
['http://normal.com[@vulndetector.com/', 'Invalid URI: Userinfo is malformed.'],
2701+
['http://[normal.com@vulndetector.com/', 'Invalid URI: Userinfo is malformed.'],
2702+
['http://normal.com@[vulndetector.com/', 'Invalid URI: Host is malformed.'],
2703+
['http://[[normal.com@][vulndetector.com/', 'Invalid URI: Userinfo is malformed.'],
2704+
['http://[vulndetector.com]', 'Invalid URI: Host is malformed.'],
2705+
['http://[0:0::vulndetector.com]:80', 'Invalid URI: Host is malformed.'],
2706+
['http://[2001:db8::vulndetector.com]', 'Invalid URI: Host is malformed.'],
2707+
['http://[malicious.com]', 'Invalid URI: Host is malformed.'],
2708+
['http://[evil.org]', 'Invalid URI: Host is malformed.'],
2709+
['http://[internal.server]', 'Invalid URI: Host is malformed.'],
2710+
['http://[192.168.1.1]', 'Invalid URI: Host is malformed.'],
2711+
['http://192.abc.1.1', 'Invalid URI: Host is malformed.'],
2712+
['http://[localhost]', 'Invalid URI: Host is malformed.'],
2713+
["\x80https://example.com", 'Invalid URI: Scheme is malformed.'],
2714+
['>https://example.com', 'Invalid URI: Scheme is malformed.'],
2715+
["http\x0b://example.com", 'Invalid URI: Scheme is malformed.'],
2716+
["https\x80://example.com", 'Invalid URI: Scheme is malformed.'],
2717+
['http>://example.com', 'Invalid URI: Scheme is malformed.'],
2718+
['0http://example.com', 'Invalid URI: Scheme is malformed.'],
2719+
];
2720+
}
2721+
2722+
/**
2723+
* @dataProvider provideLegitimateUrls
2724+
*/
2725+
public function testLegitimateUrls(string $url)
2726+
{
2727+
$request = Request::create($url);
2728+
2729+
$this->assertInstanceOf(Request::class, $request);
2730+
}
2731+
2732+
public static function provideLegitimateUrls(): array
2733+
{
2734+
return [
2735+
['http://example.com'],
2736+
['https://example.com'],
2737+
['http://example.com:8080'],
2738+
['https://example.com:8443'],
2739+
['http://user:pass@example.com'],
2740+
['http://user:pass@example.com:8080'],
2741+
['http://user:pass@example.com/path'],
2742+
['http://[2001:db8::1]'],
2743+
['http://[2001:db8::1]:8080'],
2744+
['http://[2001:db8::1]/path'],
2745+
['http://[::1]'],
2746+
['http://example.com/path'],
2747+
[':path'],
2748+
];
2749+
}
26952750
}
26962751

26972752
class RequestContentProxy extends Request

0 commit comments

Comments
 (0)