Skip to content

Commit a696cb0

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

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
@@ -344,10 +344,21 @@ public static function create(string $uri, string $method = 'GET', array $parame
344344
$server['PATH_INFO'] = '';
345345
$server['REQUEST_METHOD'] = strtoupper($method);
346346

347+
if (($i = strcspn($uri, ':/?#')) && ':' === ($uri[$i] ?? null) && (strspn($uri, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-.') !== $i || strcspn($uri, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'))) {
348+
throw new BadRequestException('Invalid URI: Scheme is malformed.');
349+
}
347350
if (false === $components = parse_url(\strlen($uri) !== strcspn($uri, '?#') ? $uri : $uri.'#')) {
348351
throw new BadRequestException('Invalid URI.');
349352
}
350353

354+
$part = ($components['user'] ?? '').':'.($components['pass'] ?? '');
355+
356+
if (':' !== $part && \strlen($part) !== strcspn($part, '[]')) {
357+
throw new BadRequestException('Invalid URI: Userinfo is malformed.');
358+
}
359+
if (($part = $components['host'] ?? '') && !self::isHostValid($part)) {
360+
throw new BadRequestException('Invalid URI: Host is malformed.');
361+
}
351362
if (false !== ($i = strpos($uri, '\\')) && $i < strcspn($uri, '?#')) {
352363
throw new BadRequestException('Invalid URI: A URI cannot contain a backslash.');
353364
}
@@ -1164,10 +1175,8 @@ public function getHost(): string
11641175
// host is lowercase as per RFC 952/2181
11651176
$host = strtolower(preg_replace('/:\d+$/', '', trim($host)));
11661177

1167-
// as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user)
1168-
// check that it does not contain forbidden characters (see RFC 952 and RFC 2181)
1169-
// use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
1170-
if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) {
1178+
// the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user)
1179+
if ($host && !self::isHostValid($host)) {
11711180
if (!$this->isHostValid) {
11721181
return '';
11731182
}
@@ -2233,4 +2242,21 @@ private function isIisRewrite(): bool
22332242

22342243
return $this->isIisRewrite;
22352244
}
2245+
2246+
/**
2247+
* See https://url.spec.whatwg.org/.
2248+
*/
2249+
private static function isHostValid(string $host): bool
2250+
{
2251+
if ('[' === $host[0]) {
2252+
return ']' === $host[-1] && filter_var(substr($host, 1, -1), \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6);
2253+
}
2254+
2255+
if (preg_match('/\.[0-9]++\.?$/D', $host)) {
2256+
return null !== filter_var($host, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4 | \FILTER_NULL_ON_FAILURE);
2257+
}
2258+
2259+
// use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
2260+
return '' === preg_replace('/[-a-zA-Z0-9_]++\.?/', '', $host);
2261+
}
22362262
}

Tests/RequestTest.php

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2303,9 +2303,9 @@ public function createRequest(): Request
23032303
Request::setFactory(null);
23042304
}
23052305

2306-
#[DataProvider('getLongHostNames')]
2307-
public function testVeryLongHosts($host)
2306+
public function testVeryLongHosts()
23082307
{
2308+
$host = 'a'.str_repeat('.a', 40000);
23092309
$start = microtime(true);
23102310

23112311
$request = Request::create('/');
@@ -2346,14 +2346,6 @@ public static function getHostValidities()
23462346
];
23472347
}
23482348

2349-
public static function getLongHostNames()
2350-
{
2351-
return [
2352-
['a'.str_repeat('.a', 40000)],
2353-
[str_repeat(':', 101)],
2354-
];
2355-
}
2356-
23572349
#[DataProvider('methodIdempotentProvider')]
23582350
public function testMethodIdempotent($method, $idempotent)
23592351
{
@@ -2726,6 +2718,67 @@ public function testReservedFlags()
27262718
$this->assertNotSame(0b10000000, $value, \sprintf('The constant "%s" should not use the reserved value "0b10000000".', $constant));
27272719
}
27282720
}
2721+
2722+
#[DataProvider('provideMalformedUrls')]
2723+
public function testMalformedUrls(string $url, string $expectedException)
2724+
{
2725+
$this->expectException(BadRequestException::class);
2726+
$this->expectExceptionMessage($expectedException);
2727+
2728+
Request::create($url);
2729+
}
2730+
2731+
public static function provideMalformedUrls(): array
2732+
{
2733+
return [
2734+
['http://normal.com[@vulndetector.com/', 'Invalid URI: Userinfo is malformed.'],
2735+
['http://[[email protected]/', 'Invalid URI: Userinfo is malformed.'],
2736+
['http://normal.com@[vulndetector.com/', 'Invalid URI: Host is malformed.'],
2737+
['http://[[normal.com@][vulndetector.com/', 'Invalid URI: Userinfo is malformed.'],
2738+
['http://[vulndetector.com]', 'Invalid URI: Host is malformed.'],
2739+
['http://[0:0::vulndetector.com]:80', 'Invalid URI: Host is malformed.'],
2740+
['http://[2001:db8::vulndetector.com]', 'Invalid URI: Host is malformed.'],
2741+
['http://[malicious.com]', 'Invalid URI: Host is malformed.'],
2742+
['http://[evil.org]', 'Invalid URI: Host is malformed.'],
2743+
['http://[internal.server]', 'Invalid URI: Host is malformed.'],
2744+
['http://[192.168.1.1]', 'Invalid URI: Host is malformed.'],
2745+
['http://192.abc.1.1', 'Invalid URI: Host is malformed.'],
2746+
['http://[localhost]', 'Invalid URI: Host is malformed.'],
2747+
["\x80https://example.com", 'Invalid URI: Scheme is malformed.'],
2748+
['>https://example.com', 'Invalid URI: Scheme is malformed.'],
2749+
["http\x0b://example.com", 'Invalid URI: Scheme is malformed.'],
2750+
["https\x80://example.com", 'Invalid URI: Scheme is malformed.'],
2751+
['http>://example.com', 'Invalid URI: Scheme is malformed.'],
2752+
['0http://example.com', 'Invalid URI: Scheme is malformed.'],
2753+
];
2754+
}
2755+
2756+
#[DataProvider('provideLegitimateUrls')]
2757+
public function testLegitimateUrls(string $url)
2758+
{
2759+
$request = Request::create($url);
2760+
2761+
$this->assertInstanceOf(Request::class, $request);
2762+
}
2763+
2764+
public static function provideLegitimateUrls(): array
2765+
{
2766+
return [
2767+
['http://example.com'],
2768+
['https://example.com'],
2769+
['http://example.com:8080'],
2770+
['https://example.com:8443'],
2771+
['http://user:[email protected]'],
2772+
['http://user:[email protected]:8080'],
2773+
['http://user:[email protected]/path'],
2774+
['http://[2001:db8::1]'],
2775+
['http://[2001:db8::1]:8080'],
2776+
['http://[2001:db8::1]/path'],
2777+
['http://[::1]'],
2778+
['http://example.com/path'],
2779+
[':path'],
2780+
];
2781+
}
27292782
}
27302783

27312784
class RequestContentProxy extends Request

0 commit comments

Comments
 (0)