Skip to content
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

Prevent Service Worker as Cross-Site Proxy #15

Open
jackfrankland opened this issue May 17, 2020 · 28 comments
Open

Prevent Service Worker as Cross-Site Proxy #15

jackfrankland opened this issue May 17, 2020 · 28 comments

Comments

@jackfrankland
Copy link

jackfrankland commented May 17, 2020

As restrictions grow for third parties, it's reasonable to expect that they will look into ways in which they can piggyback off of the first party's site. Two methods come to mind to achieve this:

  • DNS record on a sub-domain (+ an SSL certificate to be hosted by third party)
  • Proxy on a sub-domain/path in the server/cdn config

In terms of privacy, having a third party masked as the first party should be something that is discouraged. Both of the above methods at least are likely to require elevated privileges for the first party to implement, and hopefully some level of careful consideration.

There is another method to achieve a similar thing though, using Service Workers, that is arguably easier to set up and requires less elevated privileges to implement:

  1. Ask the first party to host the following service worker script at first-party.com/third-party-path/sw.js:
/* sw installation goes here */

self.addEventListener('fetch', event => {
  event.respondWith(
    fetch(`https://third-party.com/?url=${encodeURIComponent(event.request.url)}`)
  );
});
  1. Ask the first party load a third party script / execute on its page:
navigator.serviceWorker.register('/third-party-path/sw.js')
  .then(registration => {
   // do whatever
  });

All subsequent requests with path first-party.com/third-party-path/ will now be proxied to the third party domain, and considered first party to the document and any CSP rules.

Proposal

Either:

  1. The origin of the Response passed in to FetchEvent.respondWith must match the origin of the FetchEvent request. This allows matching cached responses to be passed too, but not synthetic responses.
  2. The document treats the resource as having the url/origin from the propagated Response, rather than the one from the request.

*edited to take comments into consideration. Original: Cross-Site requests in Service Workers should be disallowed. First update: A fetch made within a Service Worker fetch handler must match the site (or origin?) of the request it is handling.

Considerations

  • Are there legitimate use cases to respond to requests with synthetic responses, or resources from different sites/origins?
@annevk
Copy link

annevk commented May 18, 2020

How is this less elevated? And why exactly would the third-party fetches from the service worker not be considered to be cross-origin?

(We use a similar pattern for various WHATWG Standards, see e.g., https://fetch.spec.whatwg.org/service-worker.js.)

@jackfrankland
Copy link
Author

How is this less elevated?

It definitely depends on the circumstances, but for example, if a CMS is being used that allows for custom JS libraries to be added, as is the case for AEM I believe, then someone with privileges for content creation in an organisation could implement this, rather than needing network privileges. I think though, if this type of masking is not wanted in general, then we should remove it if plausible, regardless of its ease of implementation.

And why exactly would the third-party fetches from the service worker not be considered to be cross-origin?

The fetches are considered cross-origin when the service worker makes the request. Once the response is passed on though, it is not considered cross-origin from the perspective of the document making the initial request. This applies to all sub-resource requests, iframe documents, requests within iframes etc.

The WHATWG example doesn't seem to demonstrate a cross-site or cross-origin proxy as far as I can see. It uses a service worker from a different origin as a resource, but it doesn't fetch a resource from a different origin when intercepting requests in the scope https://fetch.spec.whatwg.org/.

The use case I can see for this is to be able to cache third party resources for offline use, but in my opinion, a properly partitioned Foreign Fetch would be better suited for this (if this is a legitimate use case... I'm not saying it should be reimplemented).

@annevk
Copy link

annevk commented May 18, 2020

This applies to all sub-resource requests, iframe documents, requests within iframes etc.

Frames establish browsing contexts and therefore whether a URL goes through a service worker is a different mechanism (equivalent to the top-level document). For those navigations you cannot respond to a same-origin URL with a cross-origin resource; the Fetch Standard prevents that.

(The WHATWG example fetches a bunch of cross-origin resources from resources.whatwg.org. E.g., that's where all the style sheets come from.)

@jackfrankland
Copy link
Author

jackfrankland commented May 18, 2020

Please see the following, showing that the request for the iframe is handled by the service worker (there is no iframe resource on that actual URL, it is being intercepted by the service worker, which requests the iframe from a different site). The script is also intercepted, and you can see it has free script access to the top window. The CSP is default-src 'self'; script-src 'self' 'unsafe-inline'; worker-src 'self';

Firefox:
image

Edge Chromium:
image

I've been quite careful to ensure my testing is accurate, but there's still definitely room for error on my part 🙂

(The WHATWG example fetches a bunch of cross-origin resources from resources.whatwg.org. E.g., that's where all the style sheets come from.)

As far as I can tell, the cross-origin resource requests will not be intercepted by the service worker, because they are not in the scope of the service worker (and can not be).

@annevk
Copy link

annevk commented May 18, 2020

There's two different ways a fetch selects a service worker:

  • Scope-match: used for navigation and workers
  • Use service worker of the environment: used for subresources

This is why <link rel=stylesheet href=...cross-origin...> goes through the document's service worker but <iframe src=...cross-origin...> does not. Now if you use same-origin URLs everywhere and fetch the responses with CORS you can indeed host cross-origin content this way, but it has as much privilege escalation as including a third-party script.

@jackfrankland
Copy link
Author

There's two different ways a fetch selects a service worker:

Scope-match: used for navigation and workers
Use service worker of the environment: used for subresources
This is why goes through the document's service worker but <iframe src=...cross-origin...> does not.

Right I understand now, sorry about that, it was a gap in my knowledge that cross origin sub-resources can indeed be intercepted by a service worker registered to the same scope as the document. On the subject of the proposal (but I agree your use case is valid to this), where only same-origin URLs are used...

Now if you use same-origin URLs everywhere and fetch the responses with CORS you can indeed host cross-origin content this way, but it has as much privilege escalation as including a third-party script.

I would argue that this does give higher privileges than just a third-party script being loaded on the first-party. Sub-resources and iframes can be loaded by the third-party script, but the browser is always aware that these resources are cross-origin, and can implement restrictions accordingly. As demonstrated above, CSP is bypassed here; there must be a reason why scripts are treated differently to frames, images, fonts etc. according to CSP. In terms of privacy, I'd like to see further progress made at some point to restricting third-party scripts' capabilities - a service worker acting as a cross-site proxy may work against that progress.

Also, the registration of the service worker needs only to happen on one page of the first party site. On subsequent navigations, a proxied iframe only needs to be present in the document for the third party to be loaded under the guise of first party, rather than an explicit script.

@jackfrankland
Copy link
Author

To still allow your use case, the proposal could be amended to something like:

A fetch made within a service worker fetch handler must match the origin of the event.request

@annevk
Copy link

annevk commented May 18, 2020

I don't really see how this relates to CSP. If you want CSP to be enforced and use service workers, you also need to set it for the service worker.

@jackfrankland
Copy link
Author

I wasn't intending for CSP to be seen as very relevant to the discussion, I was just using it as an argument for why a third party being fully masked as first party is a privilege escalation over a third-party script being loaded on the first party.

@jackfrankland
Copy link
Author

I've updated the proposal in the issue post above.

@annevk would be good to know your thoughts on this - do you think there are legitimate use cases to allow a service worker to fetch a resource from a site / origin that is different to the resource being requested by the document?

@annevk
Copy link

annevk commented May 19, 2020

Yeah, if you include an image from elsewhere on the web it would be annoying if that did not work offline. Or in case of the WHATWG, if our style sheets stopped working.

A fetch made within a Service Worker fetch handler must match the site (or origin?) of the request it is handling.

I don't see how you can realistically enforce this. Stack inspection is not in the cards and it wouldn't even work if you use a storage facility to launder things.

@jackfrankland
Copy link
Author

Thanks. I suppose looking at it from a different angle, the goal of this proposal is: to ensure that the document (and its CSP) can trust that the origin of a resource is correct, so it can implement security and privacy measures effectively.

Currently, Service Workers are able to supplant responses from different origins to the request, with the document behaving no differently. I personally think an attempt should be made to prevent this. I guess the first thing is to see if anyone agrees with me on this.

My first thought was to prevent cross-site fetches inside the Service Worker. As you pointed out, a Service Worker can indeed handle resource requests for different sites. To handle this, I thought maybe there would be a way within the Service Worker to ensure any fetches that occurred in a fetch handler matched the origin of the request. I definitely see now that this isn't feasible, nor would it prevent responding with a cached version of a response from a different origin.

My last thoughts on how to reach the goal would be either one of two solutions:

  1. The origin of the Response passed in to FetchEvent.respondWith must match the origin of the FetchEvent request. This allows matching cached responses to be passed too, assuming the response url remains intact, but not handcrafted responses.
  2. The document uses the url/origin from the propagated Response, rather than the one from the request.

@annevk
Copy link

annevk commented May 19, 2020

Unless you deploy CSP for all your environments on a given origin, including service workers, it won't be able to do its job. And as the document can come from a service worker, including CSP headers, if you cannot trust the service worker, you cannot trust the document.

@jackfrankland
Copy link
Author

I still see merit in the user agent making as much effort as it can to ensure origins are correct for resources. I think this would lessen the need to trust the service worker, as it wouldn't be able to supplant a resource of one origin with one of another. What need is there to give it the ability to respond to resources from different origins to the request, or handcrafted responses?

@annevk
Copy link

annevk commented May 19, 2020

Even without a service worker, how do you ensure a blob URL image is actually same-origin? (If your answer is CSP, the answer would be the same for the service worker.)

@jackfrankland
Copy link
Author

In the scenario in the original post, there's a very good chance that a CSP won't exist for the service worker. In other scenarios, a CSP isn't granular enough to prevent the service worker from responding to a first party request with a third party resource, if there is also a need for the service worker to fetch and cache third party resources. I am also not coming at this from the angle of security that the first party implements, but rather privacy for the user, regardless of whether the first party has lax policies for whatever reason. I hope to see user agent controlled restrictions on what a third party script can do on the first party also, beyond CSP's current capabilities (to, say, disallow a third party script creating a first party blob url).

Disregarding these other things for a moment, the question still remains: is there a legitimate use case for allowing a service worker to fetch a resource from a different origin to the request, or synthesise a response for that request? And if so, is there any reason why the origin of the propagated resource on the document shouldn't be considered as the true origin?

@annevk
Copy link

annevk commented May 20, 2020

I don't think you can disregard those other things, since it's not clear to me that what you're suggesting is actually feasible.

And also, how would you meaningfully prevent such a thing or find out the true origin of a response?

@jackfrankland
Copy link
Author

jackfrankland commented May 20, 2020

If the origin of response.url in fetchEvent.respondWith(responsePromise) does not match the origin of fetchEvent.request.url, then an error could be thrown.

If that doesn't seem like a good idea, then I think this discussion is relevant to how a response from a service worker should be treated: https://bugzilla.mozilla.org/show_bug.cgi?id=1222008. Please note I haven't read through all of it, but from what I understand, there is already a concept of treating the response in a similar manner to a redirect.

@annevk
Copy link

annevk commented May 20, 2020

@jackfrankland I don't see how that helps. What prevents the service worker from creating a synthetic response containing the bytes of the other origin?

@annevk
Copy link

annevk commented May 20, 2020

(If you follow the link that bug you'll find I've made the change to Fetch. I was one of the people who designed how Fetch and Service Workers interoperate. I don't want to make an argument from authority, but I do feel like do not need to read up on this.)

@jackfrankland
Copy link
Author

I was definitely aware of that already, and didn't mean for it to look like I was referencing something you should need to read, but rather referencing a previous discussion that seems to be focused around a similar concept of treating the service worker response as a redirect: there's much more chance that I'm wrong about that being feasible.

What prevents the service worker from creating a synthetic response containing the bytes of the other origin?

The url of the synthetic response wouldn't match the origin of the request.

@annevk
Copy link

annevk commented May 20, 2020

Okay, but in OP you were talking about the request URL being same-origin, e.g., /third-party/something and the service worker mapping that to another origin. A synthetic response is same-origin with the service worker.

But generally disallowing that would prevent redirects across CDNs and such, which seems bad. And also imbues more authority into the document than it actually has, as the service worker could just rewrite it.

@jackfrankland
Copy link
Author

Okay, but in OP you were talking about the request URL being same-origin, e.g., /third-party/something and the service worker mapping that to another origin. A synthetic response is same-origin with the service worker.

Yeah, the first option as a solution would disallow synthetic responses, by a different means to origin check if necessary - I naively thought that the origin is taken from the url, which in that case would be an empty string.

But generally disallowing that would prevent redirects across CDNs and such, which seems bad. And also imbues more authority into the document than it actually has, as the service worker could just rewrite it.

Are you saying that the use case for allowing a service worker to fetch a resource from a different origin is to offload that logic from the document, so it doesn't need to care about different origins when fetching resources? In case there's confusion, by true origin I mean only the origin of the network request made by the client, not origins further upstream.

@TanviHacks TanviHacks added the agenda+ Request to add this issue to the agenda of our next telcon or F2F label Jun 8, 2020
@johnwilander
Copy link

In the case of WebKit, we partition ServiceWorkers. Is that something that would change your analysis, Jack?

@jackfrankland
Copy link
Author

I don't believe so, if I'm understanding you correctly.

Essentially, I find it odd that a request can be made for a same-origin resource or iframe, and that the response can be from a different origin, while affording it the same privacy rules on the document as if it were same-origin. In this way, the functionality is almost equivalent to a server-side proxy, but with arguably an easier install process. This issue also assumes that it is generally undesirable for trackers to be more prevalently integrated into first party origins via server-side proxies; but there's not much a user agent can do about that (while it can do something about this).

It could be argued that from a tracking point of view, a third party script is easily included on the first party and it can already do a lot. But, there are other proposals that aim to limit the ability of third party scripts - and perhaps having this service worker mechanism available as an option will allow third parties to circumvent these efforts.

I would be interested to hear if you share this view in any way, and very happy to be told I am off-base in my concerns :)

@jackfrankland
Copy link
Author

Just thinking aloud here... is it possible for a tracker to have their service worker installed onto a network of sites, and for the installation status of the service worker to be used as a tracking vector as part of a series of navigation redirections? With requests to the cross-origin tracker allowed within the service worker, information about this status can easily be sent too. Because the origins of the navigation requests are not the tracker, it can more easily avoid being included in the tracker list.

@TanviHacks TanviHacks removed the agenda+ Request to add this issue to the agenda of our next telcon or F2F label Jun 10, 2020
@TanviHacks TanviHacks added the agenda+ Request to add this issue to the agenda of our next telcon or F2F label Jun 22, 2020
@hober hober removed the agenda+ Request to add this issue to the agenda of our next telcon or F2F label Jul 6, 2020
@TanviHacks
Copy link
Member

@jackfrankland and @annevk - Would you like to keep this proposal open? I know user agents are moving towards partitioning Services Workers. Is there more to discuss here? Thanks!

@eligrey
Copy link
Member

eligrey commented May 11, 2024

Are there legitimate use cases to respond to requests with synthetic responses, or resources from different sites/origins?

This would help site owners enforce network controls (other than CSP) on their site. For example, a ServiceWorker could be used enforce a privacy policy that depends on user privacy choices determined at runtime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants