Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 30 additions & 4 deletions src/Symfony/Component/HttpFoundation/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -351,10 +351,21 @@ public static function create(string $uri, string $method = 'GET', array $parame
$server['PATH_INFO'] = '';
$server['REQUEST_METHOD'] = strtoupper($method);

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

$part = ($components['user'] ?? '').':'.($components['pass'] ?? '');

if (':' !== $part && \strlen($part) !== strcspn($part, '[]')) {
throw new BadRequestException('Invalid URI: Userinfo is malformed.');
}
if (($part = $components['host'] ?? '') && !self::isHostValid($part)) {
throw new BadRequestException('Invalid URI: Host is malformed.');
}
if (false !== ($i = strpos($uri, '\\')) && $i < strcspn($uri, '?#')) {
throw new BadRequestException('Invalid URI: A URI cannot contain a backslash.');
}
Expand Down Expand Up @@ -1151,10 +1162,8 @@ public function getHost(): string
// host is lowercase as per RFC 952/2181
$host = strtolower(preg_replace('/:\d+$/', '', trim($host)));

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

return $this->isIisRewrite;
}

/**
* See https://url.spec.whatwg.org/.
*/
private static function isHostValid(string $host): bool
{
if ('[' === $host[0]) {
return ']' === $host[-1] && filter_var(substr($host, 1, -1), \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6);
}

if (preg_match('/\.[0-9]++\.?$/D', $host)) {
return null !== filter_var($host, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4 | \FILTER_NULL_ON_FAILURE);
}

// use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
return '' === preg_replace('/[-a-zA-Z0-9_]++\.?/', '', $host);
}
}
79 changes: 67 additions & 12 deletions src/Symfony/Component/HttpFoundation/Tests/RequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2237,11 +2237,9 @@ public function testFactory()
Request::setFactory(null);
}

/**
* @dataProvider getLongHostNames
*/
public function testVeryLongHosts($host)
public function testVeryLongHosts()
{
$host = 'a'.str_repeat('.a', 40000);
$start = microtime(true);

$request = Request::create('/');
Expand Down Expand Up @@ -2284,14 +2282,6 @@ public static function getHostValidities()
];
}

public static function getLongHostNames()
{
return [
['a'.str_repeat('.a', 40000)],
[str_repeat(':', 101)],
];
}

/**
* @dataProvider methodIdempotentProvider
*/
Expand Down Expand Up @@ -2667,6 +2657,71 @@ public function testReservedFlags()
$this->assertNotSame(0b10000000, $value, \sprintf('The constant "%s" should not use the reserved value "0b10000000".', $constant));
}
}

/**
* @dataProvider provideMalformedUrls
*/
public function testMalformedUrls(string $url, string $expectedException)
{
$this->expectException(BadRequestException::class);
$this->expectExceptionMessage($expectedException);

Request::create($url);
}

public static function provideMalformedUrls(): array
{
return [
['http://normal.com[@vulndetector.com/', 'Invalid URI: Userinfo is malformed.'],
['http://[normal.com@vulndetector.com/', 'Invalid URI: Userinfo is malformed.'],
['http://normal.com@[vulndetector.com/', 'Invalid URI: Host is malformed.'],
['http://[[normal.com@][vulndetector.com/', 'Invalid URI: Userinfo is malformed.'],
['http://[vulndetector.com]', 'Invalid URI: Host is malformed.'],
['http://[0:0::vulndetector.com]:80', 'Invalid URI: Host is malformed.'],
['http://[2001:db8::vulndetector.com]', 'Invalid URI: Host is malformed.'],
['http://[malicious.com]', 'Invalid URI: Host is malformed.'],
['http://[evil.org]', 'Invalid URI: Host is malformed.'],
['http://[internal.server]', 'Invalid URI: Host is malformed.'],
['http://[192.168.1.1]', 'Invalid URI: Host is malformed.'],
['http://192.abc.1.1', 'Invalid URI: Host is malformed.'],
['http://[localhost]', 'Invalid URI: Host is malformed.'],
["\x80https://example.com", 'Invalid URI: Scheme is malformed.'],
['>https://example.com', 'Invalid URI: Scheme is malformed.'],
["http\x0b://example.com", 'Invalid URI: Scheme is malformed.'],
["https\x80://example.com", 'Invalid URI: Scheme is malformed.'],
['http>://example.com', 'Invalid URI: Scheme is malformed.'],
['0http://example.com', 'Invalid URI: Scheme is malformed.'],
];
}

/**
* @dataProvider provideLegitimateUrls
*/
public function testLegitimateUrls(string $url)
{
$request = Request::create($url);

$this->assertInstanceOf(Request::class, $request);
}

public static function provideLegitimateUrls(): array
{
return [
['http://example.com'],
['https://example.com'],
['http://example.com:8080'],
['https://example.com:8443'],
['http://user:pass@example.com'],
['http://user:pass@example.com:8080'],
['http://user:pass@example.com/path'],
['http://[2001:db8::1]'],
['http://[2001:db8::1]:8080'],
['http://[2001:db8::1]/path'],
['http://[::1]'],
['http://example.com/path'],
[':path'],
];
}
}

class RequestContentProxy extends Request
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,8 @@ public function testRequestWithBadHost()
{
$this->expectException(BadRequestHttpException::class);
$kernel = $this->createMock(HttpKernelInterface::class);
$request = Request::create('http://bad host %22/');
$request = Request::create('/');
$request->headers->set('host', 'bad host %22');
$event = new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST);

$requestMatcher = $this->createMock(RequestMatcherInterface::class);
Expand Down
Loading