Closed Bug 1465402 Opened 3 years ago Closed 10 months ago

Cookies set with SameSite=strict are not sent in redirects

Categories

(Core :: Networking: Cookies, defect, P2)

60 Branch
defect

Tracking

()

RESOLVED DUPLICATE of bug 1453814

People

(Reporter: sean, Unassigned)

References

Details

(Whiteboard: [necko-triaged])

User Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:60.0) Gecko/20100101 Firefox/60.0
Build ID: 20180516032328

Steps to reproduce:

1. Visit a page through https that returns a 302, 303, or 307 response with a Set-Cookie header containing a SameSite=strict parameter, and a Location header which points to a page on the same domain, in Firefox 60.0.1
2. Wait for Firefox to load the redirect target page, i.e. the resource whose address is the value of the Location header in the original response



Actual results:

Firefox does not send the Cookie in the request headers to the redirect target page. This is in spite of the fact that it does send the Cookie in request headers for any resources on the same domain which are embedded in redirect target page. This appears to be the Firefox counterpart of Chromium bug 696204


Expected results:

Firefox should have sent the Cookie in the request headers to the redirect target page
I also had this problem. The situation is as follows:

1. User follows a link from domaina to domainb/first.html
2. domainb/first.html sets a samesite=strict cookie and redirects the user to domainb/second.html
3. The samesite=strict cookie is not sent to domainb/second.html

I would expect the samesite=strict cookie is sent to domainb/second.html, because this is a same-site request. I could reproduce this behavior in both Chrome and Firefox. If this behavior is intentional, it would be nice if it is properly documented.
Hi sbp, can you please download Firefox Nightly from here: https://nightly.mozilla.org/ and retest the problem? Thanks
Component: Untriaged → Networking: Cookies
Flags: needinfo?(sean)
Product: Firefox → Core
Hi Ovidiu, I can confirm that the problem also happens in exactly the same way as described in my original post in Firefox Nightly. The Nightly version I used to test this was 62.0a1, and according to about:buildconfig was built from commit 0ee6b755ab2ee6d2ab79b17cc97bd4e83424cbfc
Flags: needinfo?(sean)
Christoph, this might be similar to bug 1453814, can you have a look at it? There seems to be a working testcase at https://bugs.chromium.org/p/chromium/issues/detail?id=696204#c1.
Flags: needinfo?(ckerschb)
Priority: -- → P2
Whiteboard: [necko-triaged]
(In reply to Sjoerd from comment #1)
> I also had this problem. The situation is as follows:
> 
> 1. User follows a link from domaina to domainb/first.html
> 2. domainb/first.html sets a samesite=strict cookie and redirects the user
> to domainb/second.html
> 3. The samesite=strict cookie is not sent to domainb/second.html
> 
> I would expect the samesite=strict cookie is sent to domainb/second.html,
> because this is a same-site request. I could reproduce this behavior in both
> Chrome and Firefox. If this behavior is intentional, it would be nice if it
> is properly documented.

Oh, this case is somehow special because the samesite cookie gets set after the first (cross-origin) redirect which then gets redirected to the same-origin. Firefox implementation does not distinguish that case - in more detail, whenever a load encounters a cross-origin redirect, Firefox drops all cookies with the attribute samesite=strict, see [1].

I think that behavior makes sense, but I could be convinced otherwise. Nevertheless, I think that might need to be documented in the spec somewhere.

Mark, what's your take?

[1] https://dxr.mozilla.org/mozilla-central/source/netwerk/base/nsNetUtil.cpp#2193-2209
Flags: needinfo?(ckerschb) → needinfo?(mgoodwin)
Sjoerd's case is more similar to mine than it appears. The original request in my case is the result of an OAuth redirect. The OAuth provider uses a combination of http-equiv="refresh" in a <meta> element and setting window.location.href in JavaScript to perform the redirect from the payload of a 200 response. I'm not sure which one the browser is picking up first.

The pattern is something like this:

200 oau.th/login (contains meta redirect)
-> 303 websi.te/gateway sets cookie with SameSite=Strict (has Location header)
-> 200 websi.te/page (does not receive the cookie)

But if for example websi.te/page contains a stylesheet link to websi.te/style.css then the request to /style.css that the browser performs on behalf /page will indeed contain the cookie. In Sjoerd's case it's more like:

200 oth.er/page (contains a link to websi.te/first)
-> 303 websi.te/first sets cookie with SameSite=Strict (has Location header)
-> 200 websi.te/second (does not receive the cookie)

In Sjoerd's case the user is activating a link and then SameSite=Strict is set, and in my case the page is doing an automatic redirect instead of the user activating a link. I have not tested other cases such as where the original page submits a form, activates a link through JavaScript, or redirects through any of the redirect codes. I assume that since it's going over a cross-site boundary in each case the result would be the same. Visiting websi.te/gateway directly does pass the cookie on.

So the difference between this bug and bug 1453814 is that here the SameSite=Strict cookie is set after the cross-site redirect, indeed as an explicit response to the redirect, and not before.

The comment on the code linked by Christoph says that "cross-site to same-site redirect is a problem with regards to CSRF". In my case it's useful behaviour to be setting a cookie after being redirected back from an OAuth provider. After the cookie is set there are no cross-site requests. Moreover, resources embedded in websi.te/page *do* receive the cookie as mentioned above. It seems inconsistent for such resources to be receiving the cookie whereas the redirect target request (the request for websi.te/page) does not receive the cookie. All subsequent requests to the site, e.g. a user activated link from one websi.te page to another, will also receive the cookie.
(In reply to Christoph Kerschbaumer [:ckerschb] from comment #5)
> Mark, what's your take?

The Firefox behaviour in this case is closer to my intention; I can see there's utility in having cookies set and sent on redirect but, given this is also an avenue for forgery, I think strict same site cookies should not be sent in this case.

Would a lax same site cookie work for your use-case sbp?

(In reply to sbp from comment #6)
> But if for example websi.te/page contains a stylesheet link to
> websi.te/style.css then the request to /style.css that the browser performs
> on behalf /page will indeed contain the cookie. 

FWIW I think that, since we're not supposed to *set* these cookies from responses to cross site requests, there's probably a bug here...
Flags: needinfo?(mgoodwin) → needinfo?(sean)
Heya Mark. The text of Section 8.8.2 in draft-ietf-httpbis-rfc6265bis-02[1], originally written by you and Mike, advises to use Lax for a read permission cookie and Strict for a simultaneous write permission cookie. Are you advising instead to use Lax for both read and write actions?

This may make some sense. Specifically, 8.8.2 says to use SameSite=Strict for non-idempotent actions. But if all non-idempotent actions are implemented using their respective HTTP methods, POST, PUT, DELETE, then presumably Lax would be sufficient in all cases because the popup and prefetch attacks mentioned in 5.3.7.1 cannot invoke non-idempotent methods. But then what is the rationale for the advice in 8.8.2 in the first place? And what, indeed, would be the rationale for the existence of Strict if Lax is sufficient?

You say that SameSite should not be set by the browser as the result of a cross-site request. Is there wording in the specification to this end? I expected it to be in 4.1.2.7, but could not find it anywhere.

As you rightly point out, there is "utility in having cookies set and sent on redirect". But I do not believe that this is an avenue for forgery in the specific case of OAuth. When a redirect is made for the purpose of invoking an OAuth callback, the server knows authoritatively that a CSRF attack is not taking place. This is because the user is by definition logging in, and we have an OAuth token to certify the action!

What would be useful therefore is for the server to be able to tell the browser that SameSite=Strict should be not only be set in this cross-site request, but also that the resulting cookie should be sent in any subsequent same-site redirect requests. More lax than Strict, and more strict than Lax, if you will.

Perhaps there should be some discussion of this on the @httpwg/http-extensions issue tracker?

[1] https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-02
Flags: needinfo?(sean) → needinfo?(mgoodwin)
(In reply to sbp from comment #8)
> Heya Mark.

Hi.

> The text of Section 8.8.2 in draft-ietf-httpbis-rfc6265bis-02[1],
> originally written by you and Mike, advises to use Lax for a read permission
> cookie and Strict for a simultaneous write permission cookie. Are you
> advising instead to use Lax for both read and write actions?

No; I'm saying that some juggling might be needed in some specific cases if you want strictness (e.g. where your login cookies are set from a redirect chain that involves cross-site requests) because of the additional constraints.

> This may make some sense. Specifically, 8.8.2 says to use SameSite=Strict
> for non-idempotent actions. But if all non-idempotent actions are
> implemented using their respective HTTP methods, POST, PUT, DELETE, then
> presumably Lax would be sufficient in all cases because the popup and
> prefetch attacks mentioned in 5.3.7.1 cannot invoke non-idempotent methods.

Yes; it *should* be sufficient. Bitter experience teaches that tooling and people often diregard these conventions (I recall one IDE that, by default, wired GET and POST handlers together for convenience).

> But then what is the rationale for the advice in 8.8.2 in the first place?
> And what, indeed, would be the rationale for the existence of Strict if Lax
> is sufficient?

The aforementioned bitter experience ;)  Also, consider that not all attacks based on CSRF rely on state changing (e.g. cross-site script inclusion, etc.)
 
> You say that SameSite should not be set by the browser as the result of a
> cross-site request. Is there wording in the specification to this end? I
> expected it to be in 4.1.2.7, but could not find it anywhere.

See 5.4.14
Flags: needinfo?(mgoodwin)
(In reply to Mark Goodwin [:mgoodwin] from comment #9)
> some juggling might be needed

An automatic POST before 303 and Set-Cookie clears the cross-site flag:

<form method="post" action="/303">
<input type="hidden" name="cookie" value="...">
<button>Go</button>
</form>
<script>document.forms[0].submit()</script>

This pseudo-redirect could become a standard workaround if the SameSite specification does not include a clean provision to replicate its functionality.

> See 5.4.14

Thanks. This bug report can be taken as saying that Firefox does not follow the specification in that regard.
Duplicate of this bug: 1596813

I recently tested this issue and I confirm that we store cookies received during a redirect.

This is the test I wrote:

const express = require('express')
var cookieParser = require('cookie-parser')
const app = express()
app.use(cookieParser())

app.get('/', (req, res) => {
  if (req.query.wow) {
    res.send('Cookie: ' + JSON.stringify(req.cookies));
    return;
  }

  res.cookie('a', 'b', { domain: 'localhost', path: '/',secure: false, sameSite: 'strict'});
  return res.redirect(302, 'http://localhost:3000/?wow=wow');
});

app.listen(3000);

Reopen the bug if you think this issue is still valid.

Status: UNCONFIRMED → RESOLVED
Closed: 10 months ago
Resolution: --- → DUPLICATE
Duplicate of bug: 1453814
Duplicate of this bug: 1483832

The problem still exists in Firefox 78.0.2.
Cookie is stored, but the redirect request does not contain the cookie being written.
This is fomr my server side code:
cookie := http.Cookie{
Name: "__mycookie",
Value: cookieValue,
Path: "/web",
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
//Expires: expire,
}

With these settings, Firfox does not send the written session cookie upon a redirect.

Same problem in Firefox 80.0

Hi,

I got this bug too on Firefox 81.1.4 running on Android 10.

Also I noticed that this bug vanishes when clearing cookies manually or visiting a different url than the one specified in the redirect path.

Steps to reproduce:

  • Clear all nav data
  • Visit url redirecting to same domain with set cookie samesite="strict"
  • Check cookie is not sent in request headers
  • Manually delete cookies for said domain
  • Visit url from step 2
  • Notice new cookie is set and correctly sent with redirection

Instead of manually deleting cookies, you can also go to a different url on the same domain and then come back to the redirection target. Works also when only changing query parameters.

I've set up a minimal example that reproduces this problem.

Main app: https://cookie-lab-app.vercel.app/

The main app will set a cookie called "state" to a guid value. It will then redirect to a fake authentication server, passing along a "state" querystring parameter that is passed back to an authentication callback page in the main app. On that page the value of the state cookie and the value of the querystring state parameter are displayed.

When sameSite=strict the state cookie is missing after being redirected back from the auth site to the main app in Firefox. Also iOS Safari. The cookie is only missing on the first page load after the redirect; refreshing the page causes the cookie to become available. (Chrome, Edge and Safari on macOS all send the cookie even on first page load.)

Setting sameSite=lax works around the issue. Also, the issue only reproduces when using https.

(In reply to anders from comment #17)

I've set up a minimal example that reproduces this problem.

Main app: https://cookie-lab-app.vercel.app/

The main app will set a cookie called "state" to a guid value. It will then redirect to a fake authentication server, passing along a "state" querystring parameter that is passed back to an authentication callback page in the main app. On that page the value of the state cookie and the value of the querystring state parameter are displayed.

When sameSite=strict the state cookie is missing after being redirected back from the auth site to the main app in Firefox. Also iOS Safari. The cookie is only missing on the first page load after the redirect; refreshing the page causes the cookie to become available. (Chrome, Edge and Safari on macOS all send the cookie even on first page load.)

Setting sameSite=lax works around the issue. Also, the issue only reproduces when using https.

Realising now that this is probably the expected behavior. A bit strange that the browsers behave so differently.

You need to log in before you can comment on or make changes to this bug.