8000 [7.x] Fix cross-domain cookie leakage (#3018) · guzzle/guzzle@74a8602 · GitHub
[go: up one dir, main page]

Skip to content

Commit 74a8602

Browse files
[7.x] Fix cross-domain cookie leakage (#3018)
Co-authored-by: Tim Düsterhus <209270+TimWolla@users.noreply.github.com>
1 parent b720a2d commit 74a8602

File tree

5 files changed

+55
-15
lines changed

5 files changed

+55
-15
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version.
44

5+
## 7.4.3 - 2022-05-25
6+
7+
* Fix cross-domain cookie leakage
8+
59
## 7.4.2 - 2022-03-20
610

711
### Fixed

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@ composer require guzzlehttp/guzzle
6060

6161
## Version Guidance
6262

63-
| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version |
64-
|---------|------------|---------------------|--------------|---------------------|---------------------|-------|-------------|
65-
| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >= 5.3.3 |
66-
| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >= 5.4 |
67-
| 5.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >= 5.4 |
68-
| 6.x | Security fixes | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >= 5.5 |
69-
| 7.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes | >= 7.2 |
63+
| Version | Status | Packagist | Namespace | Repo | Docs | PSR-7 | PHP Version |
64+
|---------|----------------|---------------------|--------------|---------------------|---------------------|-------|--------------|
65+
| 3.x | EOL | `guzzle/guzzle` | `Guzzle` | [v3][guzzle-3-repo] | [v3][guzzle-3-docs] | No | >=5.3.3,<7.0 |
66+
| 4.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v4][guzzle-4-repo] | N/A | No | >=5.4,<7.0 |
67+
| 5.x | EOL | `guzzlehttp/guzzle` | `GuzzleHttp` | [v5][guzzle-5-repo] | [v5][guzzle-5-docs] | No | >=5.4,<7.4 |
68+
| 6.x | Security fixes | `guzzlehttp/guzzle` | `GuzzleHttp` | [v6][guzzle-6-repo] | [v6][guzzle-6-docs] | Yes | >=5.5,<8.0 |
69+
| 7.x | Latest | `guzzlehttp/guzzle` | `GuzzleHttp` | [v7][guzzle-7-repo] | [v7][guzzle-7-docs] | Yes | >=7.2.5,<8.2 |
7070

7171
[guzzle-3-repo]: https://github.com/guzzle/guzzle3
7272
[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x

src/Cookie/CookieJar.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,11 @@ public function extractCookies(RequestInterface $request, ResponseInterface $res
241241
if (0 !== \strpos($sc->getPath(), '/')) {
242242
$sc->setPath($this->getCookiePathFromRequest($request));
243243
}
244+
if (!$sc->matchesDomain($request->getUri()->getHost())) {
245+
continue;
246+
}
247+
// Note: At this point `$sc->getDomain()` being a public suffix should
248+
// be rejected, but we don't want to pull in the full PSL dependency.
244249
$this->setCookie($sc);
245250
}
246251
}

src/Cookie/SetCookie.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,10 +379,12 @@ public function matchesDomain(string $domain): bool
379379

380380
// Remove the leading '.' as per spec in RFC 6265.
381381
// https://tools.ietf.org/html/rfc6265#section-5.2.3
382-
$cookieDomain = \ltrim($cookieDomain, '.');
382+
$cookieDomain = \ltrim(\strtolower($cookieDomain), '.');
383+
384+
$domain = \strtolower($domain);
383385

384386
// Domain not set or exact match.
385-
if (!$cookieDomain || !\strcasecmp($domain, $cookieDomain)) {
387+
if ('' === $cookieDomain || $domain === $cookieDomain) {
386388
return true;
387389
}
388390

tests/Cookie/CookieJarTest.php

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ public function getMatchingCookiesDataProvider()
271271
/**
272272
* @dataProvider getMatchingCookiesDataProvider
273273
*/
274-
public function testReturnsCookiesMatchingRequests($url, $cookies)
274+
public function testReturnsCookiesMatchingRequests(string $url, string $cookies)
275275
{
276276
$bag = [
277277
new SetCookie([
@@ -386,16 +386,13 @@ public function getCookiePathsDataProvider()
386386
['/foo', '/'],
387387
['/foo/bar', '/foo'],
388388
['/foo/bar/', '/foo/bar'],
389-
['foo', '/'],
390-
['foo/bar', '/'],
391-
['foo/bar/', '/'],
392389
];
393390
}
394391

395392
/**
396393
* @dataProvider getCookiePathsDataProvider
397394
*/
398-
public function testCookiePathWithEmptySetCookiePath($uriPath, $cookiePath)
395+
public function testCookiePathWithEmptySetCookiePath(string $uriPath, string $cookiePath)
399396
{
400397
$response = (new Response(200))
401398
->withAddedHeader(
@@ -407,13 +404,45 @@ public function testCookiePathWithEmptySetCookiePath($uriPath, $cookiePath)
407404
"bar=foo; expires={$this->futureExpirationDate()}; domain=www.example.com; path=foobar;"
408405
)
409406
;
410-
$request = (new Request('GET', $uriPath))->withHeader('Host', 'www.example.com');
407+
$request = (new Request('GET', "https://www.example.com{$uriPath}"));
411408
$this->jar->extractCookies($request, $response);
412409

413410
self::assertSame($cookiePath, $this->jar->toArray()[0]['Path']);
414411
self::assertSame($cookiePath, $this->jar->toArray()[1]['Path']);
415412
}
416413

414+
public function getDomainMatchesProvider()
415+
{
416+
return [
417+
['www.example.com', 'www.example.com', true],
418+
['www.example.com', 'www.EXAMPLE.com', true],
419+
['www.example.com', 'www.example.net', false],
420+
['www.example.com', 'ftp.example.com', false],
421+
['www.example.com', 'example.com', true],
422+
['www.example.com', 'EXAMPLE.com', true],
423+
['fra.de.example.com', 'EXAMPLE.com', true],
424+
['www.EXAMPLE.com', 'www.example.com', true],
425+
['www.EXAMPLE.com', 'www.example.COM', true],
426+
];
427+
}
428+
429+
/**
430+
* @dataProvider getDomainMatchesProvider
431+
*/
432+
public function testIgnoresCookiesForMismatchingDomains(string $requestHost, string $domainAttribute, bool $matches)
433+
{
434+
$response = (new Response(200))
435+
->withAddedHeader(
436+
'Set-Cookie',
437+
"foo=bar; expires={$this->futureExpirationDate()}; domain={$domainAttribute}; path=/;"
438+
)
439+
;
440+
$request = (new Request('GET', "https://{$requestHost}/"));
441+
$this->jar->extractCookies($request, $response);
442+
443+
self::assertCount($matches ? 1 : 0, $this->jar->toArray());
444+
}
445+
417446
private function futureExpirationDate()
418447
{
419448
return (new DateTimeImmutable)->add(new DateInterval('P1D'))->format(DateTime::COOKIE);

0 commit comments

Comments
 (0)
0