Skip to content

[HttpFoundation] Send cookies using header() to fix "SameSite" ones #25348

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 22, 2018

Conversation

nicolas-grekas
Copy link
Member

@nicolas-grekas nicolas-grekas commented Dec 6, 2017

Q A
Branch? 3.4
Bug fix? yes
New feature? no
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets #25344
License MIT
Doc PR -

@@ -337,7 +337,7 @@ public function sendHeaders()
}

// headers
foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
foreach ($this->headers->allPreserveCase() as $name => $values) {
Copy link

@ThomHurks ThomHurks Dec 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current cookie code distinguishes between urlencoding the cookie value and not doing that (raw), the setcookie() function also performs some filtering and escaping as I understand it. As far as I can see in the code, the allPreserveCase() function doesn't do that but just casts the Cookie object to string and as such isn't a 1-to-1 substitute for setcookie() and setrawcookie(), right?

Edit: Ah, I see in Cookie.php that it does distinguish between raw/non-raw and performs some checks. However a double-check to see what PHP does in setcookie() might still be useful so there's no regressions, right?

@sroze
Copy link
Contributor

sroze commented Dec 6, 2017 via email

@ThomHurks
Copy link

@sroze Yes, I realized that after examining the code more closely and so I edited my comment. Still, cross-referencing the code with PHP's implementation may be a good thing to do to prevent regression in behaviour. I'm not sure how new the Cookie code is, and the code that is currently there is not actually used to generate the final cookie at the moment, so it may be incomplete/untested?

@nicolas-grekas
Copy link
Member Author

@ThomHurks here is the native implem:
https://github.com/php/php-src/blob/master/ext/standard/head.c#L84

everything looks fine to me.

@ThomHurks
Copy link

@nicolas-grekas Not the case, there are some differences that I can spot:

  • Symfony does not check the value of value against the characters =,; \t\r\n\013\014 whereas PHP does. Symfony only checks it against name and PHP checks it agains name and value.
  • When a cookie needs to be deleted, Symfony sets the max-age to 31536001 and the expiry to now - max-age but PHP just passes in expiry = 1 and max-age = 0.
  • PHP checks if the expiry date has a year greater than 9999.
  • Symfony also urlencode's name, I don't see PHP do that.
  • Symfony uses urlencode for the name vs rawurlencode for the value, not sure why that difference is there.

@sroze
Copy link
Contributor

sroze commented Dec 7, 2017 via email

@nicolas-grekas
Copy link
Member Author

I'd like to propose a plan:

  • @sroze would you like to submit a testing infra for this? Could be heavily inspired by AbstractSessionHandlerTest on 3.4
  • @ThomHurks would you like to submit a separate PR on 3.3 to align the behavior with native PHP?

@sroze
Copy link
Contributor

sroze commented Dec 7, 2017 via email

@ThomHurks
Copy link

@nicolas-grekas I will likely have time tomorrow to create a PR.

@nicolas-grekas
Copy link
Member Author

@sroze @ThomHurks any progress?

@halaei
Copy link

halaei commented Jan 12, 2018

Regarding spectre attack and suggested mitigations should this be considered as a critical security fix?

@sroze
Copy link
Contributor

sroze commented Jan 12, 2018

I don't think it has anything to do with the attacks you've mentioned. I did not get time to get ahead with this so if anybody would like until I get back to it, you're welcome 😉

@halaei
Copy link

halaei commented Jan 12, 2018

@sroze I quote the part from https://www.chromium.org/Home/chromium-security/ssca that seems to imply this is actually relevant:

Where possible, prevent cookies from entering the renderer process' memory by using the SameSite and HTTPOnly cookie attributes, and by avoiding reading from document.cookie.

@xabbuh xabbuh modified the milestones: 3.3, 3.4 Jan 29, 2018
@xabbuh
Copy link
Member

xabbuh commented Jan 29, 2018

moving to the 3.4 milestone as the last bugfix release for 3.3 was published today

@nicolas-grekas nicolas-grekas force-pushed the same-site-cookie branch 2 times, most recently from ef09ba8 to 65a6ec1 Compare April 20, 2018 18:36
@nicolas-grekas
Copy link
Member Author

nicolas-grekas commented Apr 20, 2018

Back on here, this PR now contains a second commit provided by @cvilleger, thank to him for working on it.

The Response + Cookie classes are now functionnally tested, and their behavior is a bit more aligned to PHP native one:

Symfony does not check the value of value against the characters =,; \t\r\n\013\014 whereas PHP does. Symfony only checks it against name and PHP checks it agains name and value.

It does for the name, but doesn't care for the value. HTTP allows anything so no reason to restrict like PHP.

When a cookie needs to be deleted, Symfony sets the max-age to 31536001 and the expiry to now - max-age but PHP just passes in expiry = 1 and max-age = 0.

Aligned

PHP checks if the expiry date has a year greater than 9999.

I don't see any reason, kept as is (and tested).

Symfony also urlencode's name, I don't see PHP do that.

PHP should encode. Symfony does, that's OK.

Symfony uses urlencode for the name vs rawurlencode for the value, not sure why that difference is there.

Unrelated to the PHP vs Symfony delta, kept as is.

Status: needs review

@@ -162,13 +162,13 @@ public function testCookieIsCleared()
public function testToString()
{
$cookie = new Cookie('foo', 'bar', $expire = strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true);
$this->assertEquals('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; max-age='.($expire - time()).'; path=/; domain=.myfoodomain.com; secure; httponly', (string) $cookie, '->__toString() returns string representation of the cookie');
$this->assertEquals('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; Max-Age=0; path=/; domain=.myfoodomain.com; secure; httponly', (string) $cookie, '->__toString() returns string representation of the cookie');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please update assertEquals() calls to assertSame()? (as done at ClientTest).

@fabpot
Copy link
Member

fabpot commented Apr 22, 2018

Thank you @nicolas-grekas.

@fabpot fabpot merged commit 73fec23 into symfony:3.4 Apr 22, 2018
fabpot added a commit that referenced this pull request Apr 22, 2018
…ite" ones (nicolas-grekas, cvilleger)

This PR was merged into the 3.4 branch.

Discussion
----------

[HttpFoundation] Send cookies using header() to fix "SameSite" ones

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #25344
| License       | MIT
| Doc PR        | -

Commits
-------

73fec23 [HttpFoundation] Add functional tests for Response::sendHeaders()
e350ea0 [HttpFoundation] Send cookies using header() to fix "SameSite" ones
@nicolas-grekas nicolas-grekas deleted the same-site-cookie branch April 26, 2018 21:09
This was referenced Apr 30, 2018
@reatang
Copy link

reatang commented May 2, 2018

Added the RFC document:rfc6265
The Set-Cookie Header:
https://tools.ietf.org/html/rfc6265#section-5.2.2

@Seldaek
Copy link
Member

Seldaek commented Jul 2, 2018

@nicolas-grekas you said:

Symfony also urlencode's name, I don't see PHP do that.
PHP should encode. Symfony does, that's OK.

This might be correct from a spec point of view, but FYI upgrading our app broke cookies with names "foo:bar:baz" as the : gets encoded and CloudFront's cookie whitelist does not recognize the encoded foo%3Abar as being the same cookie as foo:bar so the cookie gets dropped before reaching our servers. We could work around it but I wonder if this should be considered a regression or not.

@Seldaek
Copy link
Member

Seldaek commented Jul 2, 2018

Also a related issue, clearCookie does not allow clearing cookies without urlencoding, so a cookie set without urlencoding can not be cleared.

@nicolas-grekas
Copy link
Member Author

@Seldaek I think we can fix these. WOuld you mind opening an issue?

nicolas-grekas added a commit that referenced this pull request Jul 7, 2018
…grekas)

This PR was merged into the 3.4 branch.

Discussion
----------

[HttpFoundation] don't encode cookie name for BC

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

As reported by @Seldaek in #25348 (comment)

Commits
-------

d28949b [HttpFoundation] don't encode cookie name for BC
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.