ipfs / in-web-browsers

Tracking the endeavor towards getting web browsers to natively support IPFS and content-addressing
https://docs.ipfs.tech/how-to/address-ipfs-on-web/
MIT License
349 stars 29 forks source link

Sandbox resources loaded via a path gateway #157

Open lidel opened 4 years ago

lidel commented 4 years ago

Motivation

Websites loaded via path gateway are able to access cookies and storage of the entire domain. While we are moving to subdomain gateways (#89), requests made to path gateways will continue to lack origin isolation between content roots. Some will be redirected to subdomain ones, but we should look into other means of improving the situation.

TL;DR

  1. subdomain gateways will provide Origin isolation
  2. path gateways do not
  3. Various headers can be leveraged for limiting what can be used on the origin of path gateway.

Headers to investigate

Clear-Site-Data header

The Clear-Site-Data header clears browsing data (cookies, storage, cache) associated with the requesting website. It allows web developers to have more control over the data stored locally by a browser for their origins. – https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Clear-Site-Data

We could leverage Clear-Site-Data header and send a hint to user agent to clear any preexisting cookies and storage. This is a "nuclear option", but could incentivize users to switch to subdomain gateways when access Web APIs relying on Origin is required.

Note: this requires native subdomain support (https://github.com/ipfs/go-ipfs/issues/6498) to land first.

To purge cookies and storage without reloading any contexts, below header would be returned with every response from /ipfs/{cid} and /ipns/{foo} paths:

Clear-Site-Data: "cookies", "storage"

Content-Security-Policy

Disabling JS and various security features.

Ref. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy

Highlights:

Prior art:

Feature-Policy

Another way of disabling various APIs and behaviors

Ref. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy

TODO

lidel commented 4 years ago

Blocking cookies is a nuclear option that may break some deployments behind reverse proxies that pass all headers. We would need NoWebSecurity flag (false by default) in case someone really wants to disable things like Clear-Site-Data in contexts that provide no origin isolation.

lidel commented 4 years ago

Something to be aware of: executionContexts and wildcard directives are not recognized by Chromium (bug-898503)

lidel commented 4 years ago

Interesting fact: w3c spec suggests a discrepancy between clearing cookies and storage: https://www.w3.org/TR/2017/WD-clear-site-data-20171130/#grammardef-storage https://www.w3.org/TR/2017/WD-clear-site-data-20171130/#grammardef-cookies

According to the spec storage is supposed to be purged for origin, while cookies cleanup is listed as origin + all origins based on subdomains of the current one.

I tested behavior in Chromium 76 and Firefox 74 to see if it negatively impacts subdomain gateways in go-ipfs (https://github.com/ipfs/go-ipfs/pull/6096). This does not seem to be the case: subdomain cookies are not purged if Clear-Site-Data is present in localhost/ipfs/$cid 301 response.

lidel commented 3 years ago

Sidenote: we could reuse this on locked-down subdomain namespace https://github.com/ipfs/go-ipfs/issues/7318 (think cors.dweb.link/ipfs/) that is optimized for loading data/subresources and not website roots:

This could be greatly deduplicated and simplified, ideally, we would introduce everything (hardening path gateways; hardening/removing CORS from subdomains; support for long CIDs; support for CORS without compromising any local storage) in a single PR/release.

darobin commented 1 year ago

I would like to suggest taking the restrictions on Permissions-Policy (which was called Features-Policy when this issue was opened — @lidel mentions it at the top) further (spec, mdn). When you load a random web page, a number of features are available generally gated by some kind of permission or interaction. That is often okay (or at least it's understood to be a managed risk) but it is open to phishing or other forms of social engineering.

I think that in a pathed context, we can restrict this further and make those powerful capabilities unavailable. The header would look like this:

Permissions-Policy: accelerometer=(),ambient-light-sensor=(),attribution-reporting=(),autoplay=(),battery=(),
                    bluetooth=(),browsing-topics=(),camera=(),ch-device-memory=(),ch-downlink=(),
                    ch-dpr=(),ch-ect=(),ch-lang=(),ch-prefers-color-scheme=(),ch-rtt=(),ch-save-data=(),
                    ch-ua=(),ch-ua-arch=(),ch-ua-bitness=(),ch-ua-full=(),ch-ua-full-version=(),
                    ch-ua-full-version-list=(),ch-ua-mobile=(),ch-ua-model=(),ch-ua-platform=(),
                    ch-ua-platform-version=(),ch-ua-reduced=(),ch-ua-wow64=(),ch-viewport-height=(),
                    ch-viewport-width=(),ch-width=(),clipboard-read=(),clipboard-write=(),conversion-measurement=(),
                    cross-origin-isolated=(),direct-sockets=(),display-capture=(),document-domain=(),
                    encrypted-media=(),execution-while-not-rendered=(),execution-while-out-of-viewport=(),
                    federated-credentials=(),focus-without-user-activation=(),fullscreen=(),gamepad=(),
                    geolocation=(),gyroscope=(),hid=(),idle-detection=(),interest-cohort=(),
                    join-ad-interest-group=(),keyboard-map=(),local-fonts=(),magnetometer=(),microphone=(),
                    midi=(),navigation-override=(),otp-credentials=(),payment=(),picture-in-picture=(),
                    publickey-credentials-get=(),run-ad-auction=(),screen-wake-lock=(),serial=(),shared-autofill=(),
                    shared-storage=(),speaker-selection=(),storage-access-api=(),sync-script=(),sync-xhr=(),
                    trust-token-redemption=(),unload=(),usb=(),vertical-scroll=(),wake-lock=(),web-share=(),
                    window-placement=(),xr-spatial-tracking=()

So, errr, yeah, your eyes might be bleeding right now and I'm sorry about that. Also, that list grows all the time. It's not great. There are issues about making a blanket "remove anything that might be dangerous" mode but so far they haven't been accepted.

We could decide to keep some things allowed (like camera or sync XHR) if we're worried about breaking legit use cases. But at least this list (which is the most comprehensive I could find — I'm looking into whether there's a reliable way to get an up-to-date list) makes things pretty tight and safe, on top of CSP and clearing the data.

lidel commented 1 year ago

Pretty hardcore, but for sure will do the trick of forcing people to move to subdomain gateways.

I propose we do a test run: set Permissions-Policy, Clear-Site-Data and a strict Content-Security-Policy on ipfs.io first, in Nginx, and see if there are any screams from the distance.

If the sky does not fall, me and @hacdias can apply this to all path requests in go-libipfs/gateway library.

darobin commented 1 year ago

In addition to Permissions-Policy, I think the following would be good:

Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval' data: ; form-action 'self'; connect-src 'self' data: ; manifest-src 'none' ; object-src 'none' ; sandbox allow-forms allow-modals allow-scripts allow-top-navigation-by-user-activation
Clear-Site-Data: "cookies", "storage"
X-Content-Type-Options: nosniff
X-Frame-Options: sameorigin

Some quick notes:

I did some very superficial testing on static content, and it works. If you want to play with it locally, run this with a path to a dir to serve from:

#!/usr/bin/env node

import process from 'process';
import express from 'express';
const app = express();

app.use((req, res, next) => {
  res.set({
    'Permissions-Policy': 'accelerometer=(),ambient-light-sensor=(),attribution-reporting=(),autoplay=(),battery=(),bluetooth=(),browsing-topics=(),camera=(),ch-device-memory=(),ch-downlink=(),ch-dpr=(),ch-ect=(),ch-lang=(),ch-prefers-color-scheme=(),ch-rtt=(),ch-save-data=(),ch-ua=(),ch-ua-arch=(),ch-ua-bitness=(),ch-ua-full=(),ch-ua-full-version=(),ch-ua-full-version-list=(),ch-ua-mobile=(),ch-ua-model=(),ch-ua-platform=(),ch-ua-platform-version=(),ch-ua-reduced=(),ch-ua-wow64=(),ch-viewport-height=(),ch-viewport-width=(),ch-width=(),clipboard-read=(),clipboard-write=(),conversion-measurement=(),cross-origin-isolated=(),direct-sockets=(),display-capture=(),document-domain=(),encrypted-media=(),execution-while-not-rendered=(),execution-while-out-of-viewport=(),federated-credentials=(),focus-without-user-activation=(),fullscreen=(),gamepad=(),geolocation=(),gyroscope=(),hid=(),idle-detection=(),interest-cohort=(),join-ad-interest-group=(),keyboard-map=(),local-fonts=(),magnetometer=(),microphone=(),midi=(),navigation-override=(),otp-credentials=(),payment=(),picture-in-picture=(),publickey-credentials-get=(),run-ad-auction=(),screen-wake-lock=(),serial=(),shared-autofill=(),shared-storage=(),speaker-selection=(),storage-access-api=(),sync-script=(),sync-xhr=(),trust-token-redemption=(),unload=(),usb=(),vertical-scroll=(),wake-lock=(),web-share=(),window-placement=(),xr-spatial-tracking=()',
    'Content-Security-Policy': `default-src 'self' 'unsafe-inline' 'unsafe-eval' data: ; form-action 'self'; connect-src 'self' data: ; manifest-src 'none' ; object-src 'none' ; sandbox allow-forms allow-modals allow-scripts allow-top-navigation-by-user-activation`,
    'X-Content-Type-Options': 'nosniff',
    'X-Frame-Options': 'sameorigin',
  });
  next();
});
app.use(express.static(process.argv[2]));
app.listen(8888, () => console.warn(`Listening on http://localhost:8888`));