privacycg / proposals

New proposals in the Privacy Community Group
https://privacycg.github.io
122 stars 5 forks source link

Prevent Service Worker as Cross-Site Proxy #15

Open jackfrankland opened 4 years ago

jackfrankland commented 4 years ago

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:

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)}) ); });

  2. 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.

<sub>*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._</sub>

### Considerations
* Are there legitimate use cases to respond to requests with synthetic responses, or resources from different sites/origins?
annevk commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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

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 commented 4 years ago

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