WICG / shared-storage

Explainer for proposed web platform Shared Storage API
Other
85 stars 18 forks source link

Leaking more than log_2(|URLs|) bits of data with the selectURL gate #86

Open anisenoff opened 1 year ago

anisenoff commented 1 year ago

You can delay when the selectURL gate makes a request by delaying when the run function registered in the worklet returns. By doing this you can pass information to a server based on how quickly it receives the request relative to an earlier request, and use those delays to learn what value was stored with the API.

If you make a call to selectURL and only pass in a single URL it will not decrease the privacy budget as it is currently described since the request can only go to that one URL and log_2(1)=0, but delaying the request, based on a stored value, can still allow you to leak data that is not accounted for in the privacy budget. While the obfuscated URL or fenced frame config is returned almost immediately, the actual request can’t be made until the function returns. The simplest case involves either including a hardcoded delay or no delay before returning, although conceptually there is no reason you couldn’t use different lengths of delays to pass more information to the server (e.g., no delay, short delay, long delay) or tailor the delays to the current network conditions.

Below are links to two different websites that include the same third-party HTML in an iframe (light blue) which creates a persistent identifier that is transmitted as delays (hardcoded to be 2 seconds for this example) between requests for each of the fenced frames. The screenshots of the network waterfalls for the two sites are included below.

Website 1: https://anisenoff.github.io/sharedstorage/request.html?q=delay_header Website 2: https://www.andrew.cmu.edu/user/anisenof/sharedstorage/request.html?q=delay_header

website1 website2

Note: The links above were tested in Chrome version 114.0.5735.90

Furthermore, by allowing the resources to be loaded into iframes it appears to open up the possibility of using caching attacks, to learn what resource was loaded by a call to selectURL on subsequent visits to a site without decrementing the privacy budget, and possibly other side-channel attacks.

jkarlin commented 1 year ago

Hi anisenoff, thanks for the report! You're absolutely right that timing conveys extra information. The issue here is that Shared Storage requires private rendering. It's not enough to select the ad privately, it must also be rendered privately. We can accompish that one in of a few ways. We could require that all of the urls are prefetched as web bundles, we could require that the documents are fetched from some CDN that has a trusted policy that it won't leak its logs, we could utilize some sort of private information retrieval service, we could require that the server exists in a trusted execution environment running trusted code, or some mix of the above.

Right now, we're allowing selectURL() responses to be rendered in regular iframes. In time, we will require that they are rendered in fenced frames, and the fenced frames will require some sort of network/timing protection as well.

anisenoff commented 5 months ago

As a follow up on this, another way of leaking information is to intentionally crash (or not crash) the worklet before the selectURL function returns based on information stored in shared storage. My understanding is that there would be no deduction from the privacy budget as the frame would not be navigated. Also, if a single URL was passed in to begin with there would be no deduction regardless.

If this code is nested in an iframe from a different domain the process can be repeated multiple times to leak further bits of information. This means that instead of looking for delays between requests you can look at if the request happened at all, meaning the rate at which information can be leaked is not bounded by a delay that is noticeable over the internet.

jkarlin commented 5 months ago

As a follow up on this, another way of leaking information is to intentionally crash (or not crash) the worklet before the selectURL function returns based on information stored in shared storage. My understanding is that there would be no deduction from the privacy budget as the frame would not be navigated. Also, if a single URL was passed in to begin with there would be no deduction regardless.

Yep, true. This should also be resolved by fixing the network leak. We could resolve with the default url if the worklet crashes, but I'm not sure that actually fixes any problems while the network leak exists. I'm also unsure of what developers would expect/prefer to happen on worklet crash (don't resolve, or resolve to default).

anisenoff commented 5 months ago

I can see how the potential solutions from your first response would work for static content like images. If the content loaded from the result of selectURL was something like an HTML file that could then make additional requests to a different server wouldn’t that mean that all network traffic from the iframe or fenced frame being used would need to face the same requirements as the initial URL that was selected?

jkarlin commented 5 months ago

Yes, all content in the frame would need to be fetched via some trusted mechanism.

anisenoff commented 5 months ago

Thank you for the clarification. Just to follow up on my note about crashing the worklet from before. With the versions of Chrome I've been using it appears that when the worklet crashes the page that called it (from the same origin) also crashes. My understanding is, that means it would be fairly easy to circumvent any protection that solely focuses on the network traffic of the iframe/fenced frame loaded by selectURL.

jkarlin commented 5 months ago

Interesting. How are you crashing the worklet?

anisenoff commented 5 months ago

I've had a few things work. The simplest way was creating and printing an array, but other more common strategies for crashing a page, like making a massive array and doing computations for each element, also worked for me.

jkarlin commented 5 months ago

Ack, thanks! This is due to the worklet living in the same process as the iframe that's hosting it. We intend to isolate the worklet into its own process (rather than sharing with other documents of the same origin) but we first need to make sure the resource cost is acceptable.

I want to add though that while this particular side-channel can be mitigated, not all can. Thankfully, many abuses such as these (intentionally crashing a lot, using lots of cpu, lots of network, etc.) leave a trace that are ripe for after-the-fact analysis to find bad-faith actors, that the browser might then act upon.