w3c-fedid / FedCM

A privacy preserving identity exchange Web API
https://w3c-fedid.github.io/FedCM/
Other
378 stars 73 forks source link

Why Sec-FedCM-CSRF and not Sec-Fetch-Mode #320

Open martinthomson opened 2 years ago

martinthomson commented 2 years ago

Sec-Fetch-Mode seems purpose-built for this sort of thing. Adding another header field doesn't really help a lot.

(A server will naturally ignore either a new Sec-Fetch-Mode value or the Sec-FedCM-CSRF thing. The value of the former is that it will compress better and it reuses an existing mechanism.

npm1 commented 2 years ago

Is the proposal to add a new mode for Sec-Fetch-Mode, which would be added to all browser requests within the FedCM API? That seems reasonable to me.

cbiesinger commented 2 years ago

Hmm yeah looking at https://fetch.spec.whatwg.org/#concept-request-mode it seems like adding a fedcm method might be best. @annevk do you have any thoughts on that?

domenic commented 2 years ago

I'm not sure about this. The fetch mode concept is currently used to figure out major modes of the processing model for fetch, in terms of what results in network errors, what kind of security properties are required, how credentials are processed, whether early hints are respected, whether responses are opaque, etc.

I can't find anywhere in the spec that actually invokes fetch in a rigorous way, e.g. says what request input is used to the fetch algorithm (this seems very bad!!). But I am hoping that the fetch that this spec does is just a normal "cors"-mode fetch. So inventing a new mode doesn't seem right, in that case.

npm1 commented 2 years ago

Hmm thanks for the context. @martinthomson based on the above I suspect we should keep the new header? WDYT?

bvandersloot-mozilla commented 2 years ago

I can't find anywhere in the spec that actually invokes fetch in a rigorous way,

This was one of Anne's complaints as well. I think that it started to get defined in sections like check the root manifest, but isn't quite finished.

This spec doesn't actually do a normal "cors"-mode fetch- it has two types of request that are more specific. One is credential-less and referrer-less and another is credentialed.

One way forward that makes sense to me is making two new Sec-Fetch-Mode values, "fedcm-credentialless" and "fedcm-credentialed", which each specify the correct behavior within the constraints of Fetch. This also removes the need for the Sec-FedCM-CSRF header.

domenic commented 2 years ago

I hope neither of those proposed fetches bypass CORS, and I hope even more ardently that neither of them rise to the level of behavioral differences that would require an entirely new fetch mode!!

Instead, it would make most sense if they were normal, "cors"-mode fetches, which set the referrer policy and credentials mode fields of the request appropriately.

bvandersloot-mozilla commented 2 years ago

Instead, it would make most sense if they were normal, "cors"-mode fetches, which set the referrer policy and credentials mode fields of the request appropriately.

Okay, that would work. Then we do still need the Sec-FedCM-CSRF header.

martinthomson commented 2 years ago

Looking at this more closely, I don't think that the header is needed.

A fetch with the "cors" mode ensures that the content of a resource is not readable to a random site that invokes fetch() unless the (IDP) site deliberately enables access. To ensure that only the browser reads the response, the site needs to do exactly nothing. The site can use fetch headers to perform checks, and some amount of defense-in-depth is sensible here[^1], but the extra check is redundant.

For requests that mutate state (logout, token acquisition), we can insist upon a preflight by explicitly setting the use-CORS-preflight flag. But that doesn't get us what is needed. As far as I can tell, this is where things get a little sketchy because sites can set a referrer policy that prevents the inclusion of an Origin[^2]. So a missing Origin is not a strong signal that the request comes from the UA.

An alternative is to use the CSRF token defense used by most sites: include a value in an earlier request that proves that the request is genuine.

In all cases, this only requires that the endpoint that the browser interacts have a URL with sufficient high entropy as to be sufficient hard to guess by a random site that invokes fetch[^3]. I might even claim that this provides better defense than a passive signal like Sec-FedCM-CSRF because it doesn't require an additional check, though I guess that you could build an endpoint that doesn't pay any attention to a high-entropy URL component.

[^1]: You probably want to add X-Content-Type-Options: nosniff to the response. Then check Origin and Sec-Fetch-Dest and so forth.

[^2]: I can't see why we would allow script to make a cross-site request without Origin, so I might be missing something: the fetch spec is, as always, inscrutable.

[^3]: The simplest way to implement something like this is to have a per-user URL, with the URL encoding the identity of the user, a nonce, and an authentication tag; see also https://www.w3.org/TR/capability-urls/. Add a key identifier or version number and you can use the same symmetric key for many users.

cbiesinger commented 2 years ago

How would you provide an endpoint with a high-entropy URL, given that sites can just read it using a server-side proxying script?

@domenic since all of these fetches are browser-mediated and also we don't expose info to the website unless the user gives permission (by selecting an account), we do bypass CORS

cbiesinger commented 2 years ago

Also, in practice, even if we did use CORS, I'm sure IDPs will have to give access to everyone anyway since there's lots of IDPs and I doubt they want to run their CORS checks against the list every time.

domenic commented 2 years ago

Bypassing CORS is VERY BAD and MUST NOT BE DONE for new features.

martinthomson commented 2 years ago

I don't want this to seem facetious, but we bypass CORS for DoH. That said, I agree with @domenic in this case: this can use CORS.

cbiesinger commented 2 years ago

Well, that is a very strong statement. We can probably support CORS, but why do you say that not using it is so bad in this case?

cbiesinger commented 2 years ago

Also, should we require CORS even for the uncredentialed requests?

cbiesinger commented 2 years ago

To be honest, requiring CORS would make security worse here IMO because then the accounts endpoint becomes world-readable from websites instead of only to the request mediated by the UA.

martinthomson commented 2 years ago

requiring CORS [...] the accounts endpoint becomes world-readable

With CORS, the resource decides what is and isn't readable. By default (except under some narrow conditions), content is not readable by any site. The browser can see it, but it won't pass that information on unless the site explicitly permits it.

yi-gu commented 2 years ago

We don't want the IDP to know about the RP when fetching the accounts so at least CORS cannot be used there. In addition, since the "*" wildcard cannot be specified when responding to a credentialed requests request, the token request cannot use CORS, right? Otherwise the IDP must specify ALL the RP origins.

More importantly, there are 3 special things about FedCM API:

  1. the credentialed response is not visible to the RP's process with site isolation
  2. the URLs are not controlled by the RP (potential attacker), but are described in the IDP's manifest. i.e. they are fully controlled by the potential victim.
  3. it's true that the UI is triggered by a script running on the RP, however the UI is closer to the IDP than to the RP w.r.t. source and access (especially before user granting permission). e.g. when fetching the user picture from the IDP to display on the UI, the CSP img-src directive on the RP should not be applied because it's never exposed / used on the RP.

Therefore I think the current "Sec-" header without CORS solution makes more sense in this case.

domenic commented 2 years ago

I'd encourage you to involve the security teams at your browsers if you are thinking of introducing another hole in the same origin model by bypassing CORS. We've previously gotten consensus across the web community that such violations are not acceptable, and I'm disappointed to see people trying to bypass it yet again. I don't really have the energy to continue litigating these issues, so I am just going to hope that such features get blocked in per-browser security review and cross-browser TAG review, in accordance with previous resolutions.

bvandersloot-mozilla commented 2 years ago

We don't want the IDP to know about the RP when fetching the accounts so at least CORS cannot be used there. In addition, since the "*" wildcard cannot be specified when responding to a credentialed requests request, the token request cannot use CORS, right? Otherwise the IDP must specify ALL the RP origins.

CORS preflight requests are sent with Referer-Policy "same-origin" which seems acceptable. The Origin header will only be sent if the response tainting gets set to "cors", which I think we can manage to avoid for our non-credentialed requests. Also, Access-Control-Allow-Origin may be taken from the Origin request header.

I think it is entirely possible for this request to be CORS-protected, and it makes sense to use it since the result does end up in the RP's content process.

Scrolling back, I think I agree with Martin that we don't even need the Sec-FedCM-CSRF header. We already have a high-entropy component in the request to fetch a token (nonce), so all of that discussion of high-entropy URL components is moot. What is the property we are defending by making sure the endpoints are only hit by the browser API? I can't come up with a good answer, but I admit I haven't gone to the scrollback of the original inclusion.

bvandersloot-mozilla commented 2 years ago

CORS preflight requests are sent with Referer-Policy "same-origin" which seems acceptable. The Origin header will only be sent if the response tainting gets set to "cors", which I think we can manage to avoid for our non-credentialed requests.

Looking more closely, I don't think this is actually true. It looks like our non-credentialed requests would hit this fallthrough case. Unless there is a mechanism for suppressing the Origin header in CORS that I am missing, this is bad news.

annevk commented 2 years ago

-Mode indeed seems wrongish, but what about -Dest? This does seem like it might warrant a new destination type.

bvandersloot-mozilla commented 2 years ago

It looks like our non-credentialed requests would hit this fallthrough case. Unless there is a mechanism for suppressing the Origin header in CORS that I am missing, this is bad news.

Continuing the conversation with myself. I think this is fine if we perform the uncredentialed fetches with a de-priveleged client: one whose origin is opaque. This would set the Origin header to null for those requests and be fetch-spec compatible. We have the idea of a Null security principal in Gecko that is useful for de-privileging.

-Mode indeed seems wrongish, but what about -Dest? This does seem like it might warrant a new destination type.

I like -Dest the most of the proposed options (-Dest, -Mode, and Sec-FedCM-CSRF). This also adds the ability for a developer to only allow "credential" requests and not arbitrary fetches using their CSP.

domfarolino commented 2 years ago

Drive-by:

I think this is fine if we perform the uncredentialed fetches with a de-priveleged client: one whose origin is opaque. This would set the Origin header to null for those requests and be fetch-spec compatible.

Out of curiosity, what would be the need for nulling out the Origin for the uncredentialed request? IIUC, this is the request with which we're specifically OK with exposing the RP to the IDP (via the client id), right? So is there a harm in keeping the origin too? Does that expose any more information?

npm1 commented 2 years ago

I agree that there should not be a problem with the uncredentialed requests here, and I think they can use regular fetch. The complicated one is the credentialed one, which is one where we might need fetch expert help. I don't think doing a regular CORS fetch (with Origin being the RP) would work. As Christian pointed out, that would give the RPs the power to do those fetches outside of FedCM and read the accounts information.

bvandersloot-mozilla commented 2 years ago

Drive-by:

I think this is fine if we perform the uncredentialed fetches with a de-priveleged client: one whose origin is opaque. This would set the Origin header to null for those requests and be fetch-spec compatible.

Out of curiosity, what would be the need for nulling out the Origin for the uncredentialed request? IIUC, this is the request with which we're specifically OK with exposing the RP to the IDP (via the client id), right? So is there a harm in keeping the origin too? Does that expose any more information?

I was specifically talking about the requests for the manifests, which should not expose the RP to the IDP.

bvandersloot-mozilla commented 2 years ago

I agree that there should not be a problem with the uncredentialed requests here, and I think they can use regular fetch. The complicated one is the credentialed one, which is one where we might need fetch expert help. I don't think doing a regular CORS fetch (with Origin being the RP) would work. As Christian pointed out, that would give the RPs the power to do those fetches outside of FedCM and read the accounts information.

@npm1: That makes a lot of sense. Thanks for the clarification. If we add a new Sec-fetch-dest value of "credential", the IDP can validate that header in the request with an identical guarantee that the client is not initiating the request except through this API. And it has the added benefit of integrating into the Content Security Policy so the RP can make restrictive policy decisions as well.

annevk commented 2 years ago

The same-origin policy does not care about whether a request is credentialed. (CORS does, but that's almost entirely about the level of opt-in we require.) I'm not sure why you all are saying that is significant? Note in particular that IP-address-authenticated servers are still a thing, to my knowledge.

johannhof commented 2 years ago

The point was raised in https://github.com/fedidcg/FedCM/issues/320#issuecomment-1220163833 and I think it's central to this discussion. As to my knowledge, CORS in order to work requires the server to have knowledge of the request Origin. A central privacy guarantee in FedCM for at least one of the credentialed requests is that it happens without any association to the originating site (i.e. with a no-referrer policy).

annevk commented 2 years ago

Not necessarily, if the origin is an opaque origin, you could respond with `null`, even with credentialed fetches. Normally you wouldn't really do that, but perhaps for a dedicated endpoint it's okay.

cbiesinger commented 2 years ago

With regard to @domenic 's request to talk to the browser's security team, I want to be clear that we did do that and they were explicitly OK with not using CORS.

I think my comment that using CORS would make the accounts endpoint/token endpoint was misunderstood, I want to elaborate:

johannhof commented 2 years ago

Not necessarily, if the origin is an opaque origin, you could respond with null, even with credentialed fetches. Normally you wouldn't really do that, but perhaps for a dedicated endpoint it's okay.

What's the difference between that and no-cors, i.e. the tangible security gain for the IDP?

annevk commented 2 years ago

@cbiesinger I suspect that "security OK" does rely on a particular implementation whereby responses from the IDP are kept out of the RP process. (Similar to the DoH comment from @martinthomson above. Depending on the characteristics there might be cases where that is okay, though it seems safer to avoid altogether.)

@johannhof it means you cannot attack a non-IDP JSON resource. Either through a request you cannot normally make due to the same-origin policy or being able to read the response through a covert channel.

bvandersloot-mozilla commented 2 years ago

it means you cannot attack a non-IDP JSON resource. Either through a request you cannot normally make due to the same-origin policy or being able to read the response through a covert channel.

@annevk Sorry, but can you elaborate on this? I don't follow.

annevk commented 2 years ago

See https://fetch.spec.whatwg.org/#cors-protocol-exceptions, https://educatedguesswork.org/posts/web-security-model-cors/, and https://educatedguesswork.org/posts/web-security-model-side-channels/.

martinthomson commented 2 years ago

So I think that this entire CORS issue is bound up in confusion, partly because we have a bunch of very different requests that might be involved.

My understanding is that the accounts endpoint is something that the browser itself hits. The Origin value in that request would be null (or probably absent) in order to maintain our privacy goal (that is, the IdP does not learn about potential RPs, only RPs that the user chose). The response to that request is consumed by the browser, so there is no cross-site interaction involved. This request is also likely safe, so there is no inherent need for preflight. (You might even consider the origin of this request as the IdP itself if that helps.)

The most interesting one is the fetch that a browser makes when a login is initiated. This is a POST request that results in the IdP generating a token that can be used by the RP. For this, the IdP probably needs to know the identity of the RP, so Origin can be populated (or the body of the POST can contain the RP identity). But I don't see any reason why the RP needs to consume the response. The browser can consume the response and then provide the necessary information to the RP.

This means - in both cases - the IdP doesn't need to concern itself with the various Access-Control-Allow-... headers on the response. Those are for when the browser passes information to another site. In this case, the IdP can send responses without Access-Control-Allow-Foo and be safe in the knowledge that the response will only be usable by the browser.

As for the exceptions, which seem to be largely a preflight exemption, I don't see any reason to exempt FedCM. There is always a performance angle that argues for preflight exemption, but that really doesn't hold much water here. Pay the round trip.

domfarolino commented 2 years ago

Regarding the accounts endpoint request, my understanding matches @martinthomson's. Honestly, that endpoint seems almost like a .well-known endpoint. Maybe it's different since it is supposed to be hit with credentials (do normal .well-known requests have credentials included? FWIW one in this spec is currently using no-cors, but maybe that's bad), but the response:

That makes CORS feel like an odd property for the request since in order to use credentials it would have to know who is requesting it. Or, yeah I guess it could magically know to always return null if the requesting Origin is null, as Anne mentioned. That almost feels like a misuse of CORS, but maybe not!

martinthomson commented 2 years ago

do normal .well-known requests have credentials included?

A .well-known endpoint isn't special, at least as far as CORS is concerned. So the question would have to be answered based on who is asking, where, etc...

agektmr commented 2 years ago

I've been innocently believing that fetching FedCM related resources could happen as a first-party, because it's a separate process than the RP's. However, reading through this thread made me believe that treating those requests as third-party / cross-origin makes sense.

I tried to summarize my understanding here.

FedCM sends five different requests:

  1. Top-level manifest:
    • GET
    • Accept: application/json
    • uncredentialed.
    • RP shouldn't be disclosed.
  2. FedCM manifest:
    • GET
    • Accept: application/json
    • uncredentialed.
    • RP shouldn't be disclosed.
  3. Client metadata endpoint:
    • GET
    • Accept: application/json
    • uncredentialed.
    • Requires requesting RP's origin.
  4. Accounts list endpoint:
    • GET
    • Accept: application/json
    • credentialed.
    • RP shouldn't be disclosed.
  5. Assertion endpoint:
    • POST
    • Accept: application/x-www-form-urlencoded
    • credentialed.
    • Requires requesting RP's origin.

Some premises:

Having said these:

  1. Top-level manifest and 2. FedCM manifest can be requested with:
    • CORS preflight mode
    • Without requesting credentials
    • Setting Origin as null

For 3. Client metadata endpoint

For 4. Accounts list endpoint

For 5. Assertion endpoint

I don't think this summary answers questions in this thread but hopefully organizes them better.

johannhof commented 2 years ago

See https://fetch.spec.whatwg.org/#cors-protocol-exceptions, https://educatedguesswork.org/posts/web-security-model-cors/, and https://educatedguesswork.org/posts/web-security-model-side-channels/.

Sorry @annevk, can you be more specific as to how the IDP benefits from using CORS when the only possible response is null and without any knowledge on who triggered the request? I agree with @domfarolino that this feels like misuse.

npm1 commented 2 years ago

I have a proposal to help move this forward. Here is the rationale and proposal:

I plan to send a PR for these changes soon, hopefully by next week. Let me know if you have any questions or thoughts!

cbiesinger commented 2 years ago

For the request in the original title of this issue, I have filed issue #353, since this issue has morphed into general discussions around CORS which is largely orthogonal.

cbiesinger commented 2 years ago

OK, we have had extensive discussions on what the best way to proceed is, and here is our proposal.

First, let me summarize the fetches that are done when navigator.credentials.get() is called by the RP:

Second, some context for the proposed solution below:

Given the above, we think CORS adds no value over the Sec-FedCM-XSRF (soon to be changed to Sec-Fetch-Dest: webidentity) header. Concretely, our proposal is:

martinthomson commented 2 years ago

This is a pretty comprehensive take, but only from the perspective of the client. CORS doesn't exist primarily to protect the client; it exists so that servers can be protected from clients. Clients are allowed to be unhappy about CORS requirements when they are not the beneficiaries of the protections that it provides, but that doesn't change the fact that CORS exists to protect servers that are sometimes unaware of the mechanisms by which they are being protected.

Can you perhaps provide an analysis that supports your conclusion from this perspective?

npm1 commented 2 years ago

This is a pretty comprehensive take, but only from the perspective of the client. CORS doesn't exist primarily to protect the client; it exists so that servers can be protected from clients. Clients are allowed to be unhappy about CORS requirements when they are not the beneficiaries of the protections that it provides, but that doesn't change the fact that CORS exists to protect servers that are sometimes unaware of the mechanisms by which they are being protected.

Can you perhaps provide an analysis that supports your conclusion from this perspective?

From the perspective of protecting servers, the fetches we initiate from FedCM would not trigger preflights if we added CORS. We'd have to actually force preflights to happen, but the fact they do not happen by default means that there are existing mechanisms that can perform similar fetches without preflights. Thus, forcing preflights on FedCM would not introduce any benefits to such servers but it would introduce a lot of issues for clients as mentioned above.

cbiesinger commented 2 years ago

From a server protection perspective:

CORS would protect against a) "weird" requests that could break servers, and b) unexpected POST requests that could break servers. In principle it would also protect against requests from unexpected origins, but not here, since we would send a null origin.

However, we already have an opt-in requirement for FedCM -- the well-known file (top-level domain manifest). If it doesn't exist, or if it doesn't list the provider manifest URL, we only make two requests (we make them in parallel in Chrome): 1) A GET request to the tld manifest 2) A GET request to the provider manifest

These are standard GET requests with nothing unusual about them and so they should not affect servers in anyway.

If the well-known file does exist and list the provider manifest, then the server opted in to FedCM and it is safe to make the other requests; CORS would not offer additional protections.

As npm points out, FedCM would not trigger preflight in any case since none of our requests are special enough.

bvandersloot-mozilla commented 2 years ago

I agree that for the manifests, CORS with Origin: null is probably better as no-CORS.

However I retain reservations about not having CORS on credentialed requests (and see no reason not to use it on Origin-ful requests).

[...] as long as third-party cookies still exist [...], this would make the endpoints readable to the entire web since it would require the server to send Access-Control-Allow-Origin: null or *.

Credentialed requests with Access-Control-Allow-Origin: * must not have their results shared, per https://fetch.spec.whatwg.org/#cors-protocol-and-credentials. So third parties wouldn't be able to read the results of the requests.

Websites can easily send requests even with a null Origin using a data: URL.

Sure, but those requests would fail a CORS Check. Which means that either the request will have mode "cors" and have a network error or "no-cors" and the response will be an opaque filtered response. Tbf, I haven't thought about the "websocket" mode.

npm1 commented 2 years ago

Credentialed requests with Access-Control-Allow-Origin: * must not have their results shared, per https://fetch.spec.whatwg.org/#cors-protocol-and-credentials. So third parties wouldn't be able to read the results of the requests.

Well this is an IDP we are talking about so they will have many RPs. They can reply with ACAO: RP, sure, but that does not remove the concern of RPs reading the data outside of the FedCM API.

Sure, but those requests would fail a CORS Check. Which means that either the request will have mode "cors" and have a network error or "no-cors" and the response will be an opaque filtered response. Tbf, I haven't thought about the "websocket" mode.

They wouldn't fail the CORS check. Null is when the header is not sent, which is different from the string "null". The check will succeed if the Origin value is the string "null" and the origin of the fetch is also the string "null".

annevk commented 2 years ago

The provider manifest is provided by the get() call

Later on in the discussion it's suggested the provider manifest is at a .well-known location. Which is it?

Making .well-known some kind of same-origin policy extension is an interesting idea, but one that I think would warrant a (much) wider discussion than just this issue/group. In that if you decide to host a resource at a .well-known location you opt-in to a different security model. For server administrators that also seems a bit fraught though. Who knows how well the .well-known URL space is guarded.

npm1 commented 2 years ago

The provider manifest is provided by the get() call

Later on in the discussion it's suggested the provider manifest is at a .well-known location. Which is it?

Both. get() provides the config URL. We fetch the file in the .well-known location and check that the config URL specified in that file matches what the get() call provided. If it does not, then we reject the get() call without performing any additional fetching (i.e. we only end up fetching the file from .well-known and the config URL).

bvandersloot-mozilla commented 2 years ago

They wouldn't fail the CORS check. Null is when the header is not sent, which is different from the string "null". The check will succeed if the Origin value is the string "null" and the origin of the fetch is also the string "null".

Right, 🤦‍♂️, thank you. Good catch. I didn't realize Fetch treats opaque origins identically in this way. Seems not great, but that is why ACAO: null is warned against I suppose.

Well this is an IDP we are talking about so they will have many RPs. They can reply with ACAO: RP, sure, but that does not remove the concern of RPs reading the data outside of the FedCM API.

Fair enough. It just makes more sense to me that if a service has its RPs set up to use CORS properly it would manage to validate Sec-Fetch-Dest as well. But you are right that it is a foot-gun.

I'll defer to colleagues with more direct involvement in the Fetch spec as to whether this is an appropriate use case for "no-cors".